Load Data

TAXTAB <- data.frame(as(tax_table(psSubj), "matrix")) %>%
  rownames_to_column("Seq_ID") %>%
  select(-Seq) %>%
  mutate(
    OrgName.RDP = paste(Genus, Species),
    OrgName.Silva = paste(GenusSilva, SpeciesSilva),
    OrgName = ifelse(grepl("NA", OrgName.RDP) & !grepl("NA", OrgName.RDP),
                     OrgName.Silva, OrgName.RDP),
    OrgName = ifelse(OrgName == "NA NA", "NA", OrgName),
    OrgName = paste0(Seq_ID, ": ", OrgName)) %>%
  select(1:8, OrgName) 
head(TAXTAB)
  Seq_ID  Kingdom        Phylum       Class         Order          Family            Genus     Species
1   Seq1 Bacteria    Firmicutes  Clostridia Clostridiales Ruminococcaceae Faecalibacterium prausnitzii
2   Seq2 Bacteria Bacteroidetes Bacteroidia Bacteroidales  Bacteroidaceae      Bacteroides    vulgatus
3   Seq3 Bacteria    Firmicutes  Clostridia Clostridiales Ruminococcaceae Faecalibacterium prausnitzii
4   Seq4 Bacteria Bacteroidetes Bacteroidia Bacteroidales  Prevotellaceae       Prevotella        <NA>
5   Seq5 Bacteria Bacteroidetes Bacteroidia Bacteroidales  Bacteroidaceae      Bacteroides    vulgatus
6   Seq6 Bacteria    Firmicutes  Clostridia Clostridiales Ruminococcaceae Faecalibacterium prausnitzii
                             OrgName
1 Seq1: Faecalibacterium prausnitzii
2         Seq2: Bacteroides vulgatus
3 Seq3: Faecalibacterium prausnitzii
4                Seq4: Prevotella NA
5         Seq5: Bacteroides vulgatus
6 Seq6: Faecalibacterium prausnitzii
load("output/pairwise_dist_to_baseline_subj_16S.rda")
load("output/fpca_clust_res_abx.rda")
load("output/fpca_clust_res_diet.rda")
load("output/fpca_res.rda")
ls()
 [1] "abx_fclust"          "abx_fclust_mu"       "abx_fclust_subjFit"  "abx_intv_cols"      
 [5] "abx.rsv.subset"      "bray_to_baseline"    "bray.df"             "brayD.ihs"          
 [9] "cc_intv_cols"        "curdir"              "datadir"             "diet_fclust"        
[13] "diet_fclust_mu"      "diet_fclust_subjFit" "diet_intv_cols"      "diet.rsv.subset"    
[17] "fpca.bray.abx"       "fpca.bray.cc"        "fpca.bray.cc30"      "fpca.bray.diet"     
[21] "fpca.bray.diet30"    "intv_cols"           "jacc_to_baseline"    "jacc.df"            
[25] "jaccardD"            "keep_rsv"            "psSubj"              "SMP"                
[29] "SUBJ"                "TAXTAB"              "theme_subplot"       "uniFrac_to_baseline"
[33] "uniFrac.df"          "uniFracD.lst"       

Functional PCA

source("./fpca_funs.R")
# this is because some subjects have multiple samples take the same day
bray_to_baseline_fltr <- bray_to_baseline %>% 
  group_by(perturbation, Subject, Group, Interval, RelDay) %>%
  summarise(n = n(), dist_to_baseline = mean(dist_to_baseline)) %>%
  ungroup()
bray_to_baseline_fltr %>% filter(n > 1)

Antibiotics

bray_to_baseline_fltr %>% 
  filter(perturbation == "Abx") %>%
  filter(RelDay >= -50, RelDay <= 60) %>%
  mutate(Interval = factor(Interval, level = names(abx_intv_cols))) %>%
  ggplot(
    aes(x = RelDay, y = dist_to_baseline, 
        group = Subject, color = Interval)) +
  geom_line(aes(group = Subject), alpha = 0.7, lwd = 0.5) + 
  geom_point(alpha = 0.5, size = 1.2) + 
  scale_color_manual(values = abx_intv_cols) + 
  theme(legend.position = "bottom") + 
  guides(colour = guide_legend(override.aes = list(size=3))) 

# +
#   xlab("Days from initial antibiotic dose") +
#   ylab("Bray-Curtis distance to 7 pre-antibiotic samples") 
fpca.bray.abx <- fit_dist_to_baseline(
  bray_to_baseline_fltr %>% filter(
    perturbation == "Abx", RelDay >= -50, RelDay <= 60))

save(list = c("fpca.bray.abx"), file = "output/fpca_res.rda")
(pAbx <- bray_to_baseline_fltr %>% 
  filter(perturbation == "Abx", RelDay >= -50, RelDay <= 60) %>%
ggplot(aes(x = RelDay, y = dist_to_baseline)) +
  geom_line(
    data = fpca.bray.abx[["fitted"]],
    aes(group = Subject, x = time, y = value),
    alpha = 0.3, size = 0.7, color = "grey30") +
  geom_vline(xintercept = 0, lwd = 1, color = "orange") +
  geom_vline(xintercept = 4, lwd = 1, color = "orange") +
  geom_point(aes(color = Interval), size = 1.5, alpha = 0.7) +
  geom_line(
    data = fpca.bray.abx[["mean"]], aes(x = time, y = value),
    color = "navy", size = 2) +
  scale_color_manual(values = abx_intv_cols, name = "Interval") +
  scale_x_continuous(
    name = "Days from initial antibiotic dose",
    limits = c(NA, NA), breaks = seq(-50, 60, 10)) +
  scale_y_continuous(
    name = "Bray-Curtis distance to baseline", 
    limits = c(0.1, 0.85), breaks = seq(0.1, 0.80, 0.1)) +
  theme(text = element_text(size = 20)))

fpca.bray.abx[["fitted"]] <- fpca.bray.abx[["fitted"]] %>%
  mutate(
    Interval = ifelse(fpca.bray.abx[["fitted"]]$time < 0 , "PreAbx",
               ifelse(fpca.bray.abx[["fitted"]]$time >= 0 & fpca.bray.abx[["fitted"]]$time <= 4, "MidAbx",
                      "PostAbx")))
(pAbxDeriv <-  fpca.bray.abx[["fitted"]] %>%
  ggplot(aes(x = RelDay)) +
  geom_line(
    aes(group = Subject, x = time, y = deriv, color = Interval),
    alpha = 0.5, size = 0.7) +
  geom_vline(xintercept = 0, lwd = 1, color = "orange") +
  geom_vline(xintercept = 4, lwd = 1, color = "orange") +
  scale_color_manual(values = abx_intv_cols, name = "Interval") +
  scale_x_continuous(name = "",
    limits = c(NA, NA), breaks = seq(-50, 60, 10)) +
  ylab("Derivative"))

(pAbxLab <- bray_to_baseline_fltr %>% 
  filter(perturbation == "Abx", RelDay >= -50, RelDay <= 60) %>%
ggplot(aes(x = RelDay, y = dist_to_baseline)) +
  geom_line(
    data = fpca.bray.abx[["fitted"]],
    aes(group = Subject, x = time, y = value),
    alpha = 0.3, size = 0.7 ) +
  geom_vline(xintercept = 0, lwd = 1, color = "orange") +
  geom_vline(xintercept = 4, lwd = 1, color = "orange") +
  geom_text(
      aes(label = Subject, color = Interval), size = 4, alpha = 0.7) +
  geom_line(
    data = fpca.bray.abx[["mean"]], aes(x = time, y = value),
    color = "navy", size = 2) +
  scale_color_manual(values = abx_intv_cols, name = "Interval") +
  scale_x_continuous(
    name = "Days from initial antibiotic dose",
    limits = c(NA, NA), breaks = seq(-50, 60, 10)) +
  scale_y_continuous(
    name = "Bray-Curtis distance to baseline", 
    limits = c(0.1, 0.85), breaks = seq(0.1, 0.80, 0.1)) +
  theme(text = element_text(size = 20)))

  # geom_point(
  #     data = abx_bray %>% filter(RelDay > StabTime),
  #     color = "orange", size = 2.5) +
vp = grid::viewport(width = 0.3, height = 0.32, x = 0.07, y =0.95, just = c("left", "top"))
print(pAbx)
print(pAbxDeriv + theme_bw(base_size = 10) + theme_subplot, vp = vp)

Cluster RSVs

abxFac <- c("PreAbx", "MidAbx", "PostAbx")
abx <- subset_samples(psSubj, Abx_RelDay >= -50 & Abx_RelDay <= 60)
abx <- subset_taxa(abx, taxa_sums(abx) > 0)
abx

(replicated <- data.frame(sample_data(abx)) %>% 
  group_by(Subject, Group, Interval, Abx_RelDay) %>%
  mutate(n = n()) %>%
  ungroup() %>% filter(n > 1))
# Remove replicated samples
sample_sums(abx)[replicated$Meas_ID]

# M7869 M7886                                                                                         
# 74697 74873

# we retain a replicate with higher sample depth:

abx <- subset_samples(abx, !Meas_ID %in% c("M2243", "M7869"))
abx
#otu_table()   OTU Table:         [ 2339 taxa and 1418 samples ] 
# Asinh the data so that the counts are comparable
abx.rsv <- data.frame(asinh(as(otu_table(abx), "matrix"))) %>%
  rownames_to_column("Seq_ID") %>%
  gather(Meas_ID, abundance, -Seq_ID) %>%
  left_join(SMP) %>%
  left_join(TAXTAB) %>%
  mutate(Abx_Interval = factor(Abx_Interval, levels = abxFac)) %>%
  arrange(Seq_ID, Subject, Abx_RelDay)


thresh <- 0; num_nonzero <- 10; 
min_no_subj <- 3

keep_rsv <- abx.rsv %>%
  filter(abundance > thresh) %>%
  group_by(Subject, Seq_ID) %>%
  summarise(Freq = n()) %>%   # No. of samples per subject w/ positive rsv count 
  filter(Freq >= num_nonzero) %>%
  group_by(Seq_ID) %>%
  summarise(Freq = n()) %>% # Number of subjects w/ num_nonzero samples with counts > thresh
  filter(Freq >= min_no_subj) %>%
  arrange(desc(Freq))

length(unique(keep_rsv$Seq_ID)) # = 839

abx.rsv.subset <- abx.rsv %>%
  filter(Seq_ID %in% unique(keep_rsv$Seq_ID))

#save(list = c("keep_rsv", "abx.rsv.subset"), file = "results/fpca_clust_res_abx.rda")
abx_fclust <- fpca_wrapper(
  abx.rsv.subset,
  time_column = "Abx_RelDay",
  value_column = "abundance",
  replicate_column = "Subject",
  feat_column = "Seq_ID",
  cluster = TRUE,
  clust_min_num_replicate = 15,
  fpca_optns = NULL, fclust_optns = NULL,
  parallel = TRUE, ncores = 16)
save(list = c("abx_fclust"),
    file = "results/abx_rsv_fclust.rda")

abx_fclust_mu <- get_fpca_means(abx_fclust)
abx_fclust_subjFit <- get_fpca_fits(abx_fclust)

save(list = c("keep_rsv", "abx.rsv.subset", 
              "abx_fclust", "abx_fclust_subjFit", "abx_fclust_mu"),
    file = "output/fpca_clust_res_abx.rda")

We now find the taxa with the most variability in the response to ABX between two clusters:

# Taxa's trajectory difference between two clusters
diffBetweenClusts.abx <- abx_fclust_subjFit %>%
  group_by(Feature_ID, time, Cluster ) %>% 
  summarise(value = mean(value, na.rm = TRUE)) %>%
  spread(key = "Cluster", value = "value") %>%
  ungroup() %>% 
  mutate(diff = abs(`1` - `2`)) %>%
  group_by(Feature_ID) %>%
  summarise(
    mean_clustDist_L1 = mean(diff, na.rm = TRUE),
    sd_Clust1 = sd(`1`),
    sd_Clust2 = sd(`2`)) %>% 
  filter(
    !is.na(mean_clustDist_L1)
  ) %>% # Some taxa have a single cluster (everyone behaves similarly)
  arrange(-mean_clustDist_L1)
summary(diffBetweenClusts.abx)
  Feature_ID        mean_clustDist_L1   sd_Clust1         sd_Clust2      
 Length:228         Min.   :0.2363    Min.   :0.02904   Min.   :0.01393  
 Class :character   1st Qu.:1.2631    1st Qu.:0.20271   1st Qu.:0.20727  
 Mode  :character   Median :1.9057    Median :0.36080   Median :0.36572  
                    Mean   :1.9448    Mean   :0.57786   Mean   :0.59330  
                    3rd Qu.:2.5430    3rd Qu.:0.62558   3rd Qu.:0.75281  
                    Max.   :4.6640    Max.   :3.68735   Max.   :3.22498  
diffBetweenClusts.abx <- diffBetweenClusts.abx %>%
  filter(sd_Clust1 > median(diffBetweenClusts.abx$sd_Clust1) |
           sd_Clust2 > median(diffBetweenClusts.abx$sd_Clust2))
# Plot top 20
seq_to_plot <- diffBetweenClusts.abx$Feature_ID[1:20]
tax_to_plot <- (TAXTAB %>% column_to_rownames("Seq_ID"))[seq_to_plot, "OrgName"]
tax_to_plot
 [1] "Seq4: Prevotella NA"                 "Seq3: Faecalibacterium prausnitzii" 
 [3] "Seq59: Bacteroides NA"               "Seq68: Bacteroides NA"              
 [5] "Seq74: Clostridium_XVIII NA"         "Seq11: Bacteroides vulgatus"        
 [7] "Seq7: Faecalibacterium prausnitzii"  "Seq1: Faecalibacterium prausnitzii" 
 [9] "Seq25: Bacteroides NA"               "Seq8: Bacteroides dorei"            
[11] "Seq6: Faecalibacterium prausnitzii"  "Seq37: Faecalibacterium prausnitzii"
[13] "Seq123: NA"                          "Seq28: Faecalibacterium prausnitzii"
[15] "Seq66: Blautia NA"                   "Seq12: NA"                          
[17] "Seq49: Bifidobacterium NA"           "Seq30: Parabacteroides distasonis"  
[19] "Seq18: Bacteroides uniformis"        "Seq31: Clostridium_XVIII NA"        
seq1_subjClusters <- abx_fclust_subjFit %>%
  filter(Feature_ID == "Seq1") %>%
  select(Replicate_ID, Cluster) %>% distinct() %>%
  filter(Cluster==2)
abx2plot.fclust <- abx_fclust_subjFit %>%
  left_join(TAXTAB, by = c("Feature_ID" = "Seq_ID")) %>%
  group_by(Feature_ID) %>%
  mutate(
    n_cluster1 = sum(Cluster == 1),
    n_cluster2 = sum(Cluster == 2),
    majorityCluster = ifelse(n_cluster1 > n_cluster2, 1, 2)
  ) %>% 
  ungroup() %>%
  mutate(
    OrgName = factor(OrgName, levels = tax_to_plot),
    Subject_Cluster = ifelse(Cluster == majorityCluster, "Majority", "Minority")) 
abx2plot <- abx.rsv.subset %>% 
  filter(Seq_ID %in% seq_to_plot) %>%
  left_join(
    abx2plot.fclust %>% 
      select(-value, -time) %>%
      distinct() %>%
      rename(Seq_ID = Feature_ID,  Subject = Replicate_ID) 
  ) %>%
  mutate(
    Seq_ID = factor(Seq_ID, levels = seq_to_plot),
    OrgName = factor(OrgName, levels = tax_to_plot))
Joining, by = c("Seq_ID", "Subject", "Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "OrgName")
Column `OrgName` joining character vector and factor, coercing into character vector
abx2plot.fclust %>%
    filter(Feature_ID %in% seq_to_plot) %>%
ggplot(aes(x = time, y = value, color = Subject_Cluster)) +
  geom_line(
    aes(group = Replicate_ID),
    alpha = 0.3, size = 0.5
  ) +
  geom_smooth(se = FALSE) +
  geom_point(
    data = abx2plot,
    aes(x = Abx_RelDay, y = abundance),
    size = 0.3, alpha = 0.5
  ) +
  geom_vline(xintercept = 0, lwd = 1, color = "orange") +
  geom_vline(xintercept = 4, lwd = 1, color = "orange") +
  geom_line(
    data = abx_fclust_mu %>% 
      filter(Feature_ID %in% seq_to_plot) %>% 
      left_join(TAXTAB, by = c("Feature_ID" = "Seq_ID")) %>%
      mutate(OrgName = factor(OrgName, levels = tax_to_plot)),
    color = "red", size = 1
  ) +
  scale_color_manual(values = c( "grey17", "deepskyblue2")) +
  facet_wrap(~ OrgName, scales = "free", ncol = 4) +
  theme(strip.text = element_text(family="Helvetica-Narrow", size = 10)) +
  scale_x_continuous(name = "Days from initial antibiotic dose",
                     breaks = seq(-40, 120, 20), labels =  seq(-40, 120, 20),
                     limits = c(NA, NA)) 

Microbe clusters

Using mean response to Abx

seqClusters.abx <- fit_fpca(
  abx_fclust_majority,
  "time", "value", "Feature_ID",
  cluster = TRUE, K = 8)
write.csv(seqClusters_fpca.abx %>% select(Seq_ID, Cluster) %>% distinct() %>% 
            left_join(TAXTAB) %>% arrange(Cluster),
          file = "output/abx_seqClusters.csv")
Joining, by = "Seq_ID"
ggplot(
  seqClusters_fpca.abx, aes(x = time, y = value)) +
  geom_line(
    aes(group = Seq_ID, color = Cluster),
    size = 0.7, alpha = 0.7
  ) +
  geom_vline(xintercept = 0, lwd = 1, color = "orange") +
  geom_vline(xintercept = 5, lwd = 1, color = "orange") +
  geom_smooth(color = "grey20") +
  facet_wrap(~ Cluster, labeller = label_both, ncol = 4) +
  scale_color_brewer(palette = "Set1") +
  guides(color = guide_legend(override.aes = list(size=4)))

top_rsv <- seqClusters_fpca.abx %>%
  group_by(Seq_ID, Cluster) %>%
  summarise(mean_abnd = mean(value)) %>%
  arrange(desc(mean_abnd)) %>%
  left_join(TAXTAB %>% select(Seq_ID, OrgName, Species, Genus))
Joining, by = "Seq_ID"
n = 6; nclust = length(unique(top_genus$Cluster))
df_top_genus <- top_genus %>% 
  group_by(Cluster) %>% 
  top_n(n, wt = prev*mean_abnd) %>%
  arrange(Cluster, -prev, -mean_abnd) %>%
  select(-mean_abnd, -prev) %>% ungroup() %>%
  mutate(Cluster = paste0("Cluster", Cluster)) %>%
  mutate(idx = rep(1:n, nclust)) %>%
  spread(key = Cluster, Genus)
df_top_genus

Diet

bray_to_baseline_fltr %>% 
  filter(perturbation == "Diet", RelDay >= -30, RelDay <= 30) %>%
  mutate(Interval = factor(Interval, level = names(diet_intv_cols))) %>%
  ggplot(
    aes(x = RelDay, y = dist_to_baseline, 
        group = Subject, color = Interval)) +
  geom_line(aes(group = Subject), alpha = 0.7, lwd = 0.5) + 
  geom_point(alpha = 0.5, size = 1.2) + 
  scale_color_manual(values = diet_intv_cols) + 
  theme(legend.position = "bottom") + 
  guides(colour = guide_legend(override.aes = list(size=3))) +
  xlab("Days from diet initiation") +
  ylab("Bray-Curtis distance to 7 pre-diet samples") 

fpca.bray.diet30 <- fit_dist_to_baseline(
  bray_to_baseline_fltr %>% filter(perturbation == "Diet", RelDay >= -30, RelDay <= 30))

save(list = c("fpca.bray.abx", "fpca.bray.diet", "fpca.bray.diet30"), file = "output/fpca_res.rda")
(pDiet <- bray_to_baseline_fltr %>% 
  filter(perturbation == "Diet", RelDay >= -30, RelDay <= 30) %>%
ggplot(aes(x = RelDay, y = dist_to_baseline)) +
  geom_line(
    data = fpca.bray.diet30[["fitted"]],
    aes(group = Subject, x = time, y = value),
    alpha = 0.3, size = 0.7, color = "grey30") +
  geom_vline(xintercept = 0, lwd = 1, color = "orange") +
  geom_vline(xintercept = 4, lwd = 1, color = "orange") +
  geom_point(aes(color = Interval), size = 1.5, alpha = 0.7) +
  geom_line(
    data = fpca.bray.diet30[["mean"]], aes(x = time, y = value),
    color = "navy", size = 2) +
  scale_color_manual(values = diet_intv_cols, name = "Interval") +
  scale_x_continuous(
    name = "Days from diet initiation",
    limits = c(NA, NA), breaks = seq(-50, 60, 10)) +
  scale_y_continuous(
    name = "Bray-Curtis distance to baseline", 
    limits = c(NA, NA), breaks = seq(0.1, 0.80, 0.1)) +
  theme(text = element_text(size = 20))) 

fpca.bray.diet30[["fitted"]] <- fpca.bray.diet30[["fitted"]] %>%
  mutate(
    Interval = ifelse(fpca.bray.diet30[["fitted"]]$time < 0 , "PreDiet",
               ifelse(fpca.bray.diet30[["fitted"]]$time >= 0 & fpca.bray.diet30[["fitted"]]$time <= 4, "MidDiet",
                      "PostDiet")))
(pDietDeriv <-  fpca.bray.diet30[["fitted"]] %>%
  ggplot(aes(x = RelDay)) +
  geom_line(
    aes(group = Subject, x = time, y = deriv, color = Interval),
    alpha = 0.5, size = 0.7) +
  geom_vline(xintercept = 0, lwd = 1, color = "orange") +
  geom_vline(xintercept = 4, lwd = 1, color = "orange") +
  scale_color_manual(values = diet_intv_cols, name = "Interval") +
  scale_x_continuous(name = "",
    limits = c(NA, NA), breaks = seq(-50, 60, 10)) +
  ylab("Derivative"))

(pDietLab <- bray_to_baseline_fltr %>% 
  filter(perturbation == "Diet", RelDay >= -50, RelDay <= 60) %>%
ggplot(aes(x = RelDay, y = dist_to_baseline)) +
  geom_line(
    data = fpca.bray.diet[["fitted"]],
    aes(group = Subject, x = time, y = value),
    alpha = 0.3, size = 0.7 ) +
  geom_vline(xintercept = 0, lwd = 1, color = "orange") +
  geom_vline(xintercept = 4, lwd = 1, color = "orange") +
  geom_text(
      aes(label = Subject, color = Interval), size = 4, alpha = 0.7) +
  geom_line(
    data = fpca.bray.diet[["mean"]], aes(x = time, y = value),
    color = "navy", size = 2) +
  scale_color_manual(values = diet_intv_cols, name = "Interval") +
  scale_x_continuous(
    name = "Days from diet initiation",
    limits = c(NA, NA), breaks = seq(-50, 60, 10)) +
  scale_y_continuous(
    name = "Bray-Curtis distance to baseline", 
    limits = c(0.1, 0.85), breaks = seq(0.1, 0.80, 0.1)) +
  theme(text = element_text(size = 20)))

vp = grid::viewport(width = 0.3, height = 0.32, x = 0.07, y =0.95, just = c("left", "top"))
print(pDiet)
print(pDietDeriv + theme_bw(base_size = 10) + theme_subplot, vp = vp)

Cluster RSVs

dietFac <- c("PreDiet", "MidDiet", "PostDiet")
diet <- subset_samples(psSubj, Diet_RelDay >= -30 & Diet_RelDay <= 30)
diet <- subset_taxa(diet, taxa_sums(diet) > 0)
diet

(replicated <- data.frame(sample_data(diet)) %>% 
        group_by(Subject, Group, Diet_Interval, Diet_RelDay) %>%
        mutate(n = n()) %>% ungroup() %>% 
        filter(n > 1) %>% select(Meas_ID, Subject, Diet_RelDay, Diet_Interval))

# Remove replicated samples
sample_sums(diet)[replicated$Meas_ID]
# M7869 M7886 
# 74697 74873 

# we retain a replicate with higher sample depth:
diet <- subset_samples(diet, !Meas_ID %in% c("M2129"))
diet
# otu_table()   OTU Table:         [ 2333 taxa and 2611 samples ]
diet.rsv <- data.frame(asinh(as(otu_table(diet), "matrix"))) %>%
  rownames_to_column("Seq_ID") %>%
  gather(Meas_ID, abundance, -Seq_ID) %>%
  left_join(SMP) %>%
  left_join(TAXTAB) %>%
  mutate(Diet_Interval = factor(Diet_Interval, levels = dietFac)) %>%
  arrange(Seq_ID, Subject, Diet_RelDay)

thresh <- 0; num_nonzero <- 10; 
min_no_subj <- 3

keep_rsv <- diet.rsv %>%
  filter(abundance > thresh) %>%
  group_by(Subject, Seq_ID) %>%
  summarise(Freq = n()) %>%   # No. of samples per subject w/ positive rsv count 
  filter(Freq >= num_nonzero) %>%
  group_by(Seq_ID) %>%
  summarise(Freq = n()) %>% # Number of subjects w/ num_nonzero samples with counts > thresh
  filter(Freq >= min_no_subj) %>%
  arrange(desc(Freq))

length(unique(keep_rsv$Seq_ID)) # = 754

diet.rsv.subset <- diet.rsv %>%
  filter(Seq_ID %in% unique(keep_rsv$Seq_ID))

# save(list = c("keep_rsv", "diet.rsv.subset"), 
#      file = "output/fpca_clust_res_diet.rda")
diet_fclust <- fpca_wrapper(
  diet.rsv.subset,
  time_column = "Diet_RelDay",
  value_column = "abundance",
  replicate_column = "Subject",
  feat_column = "Seq_ID",
  cluster = TRUE,
  clust_min_num_replicate = 15,
  fpca_optns = NULL, fclust_optns = NULL,
  parallel = TRUE, ncores = 16)

diet_fclust_mu <- get_fpca_means(diet_fclust)
diet_fclust_subjFit <- get_fpca_fits(diet_fclust)

save(list = c("keep_rsv", "diet.rsv.subset", 
              "diet_fclust", "diet_fclust_subjFit", "diet_fclust_mu"),
    file = "output/fpca_clust_res_diet.rda")

We now find the taxa with the most variability in the response to Diet between two clusters:

# Taxa's trajectory difference between two clusters
load("output/fpca_clust_res_diet.rda")
diffBetweenClusts.diet <- diet_fclust_subjFit %>%
  group_by(Feature_ID, time, Cluster ) %>% 
  summarise(value = mean(value, na.rm = TRUE)) %>%
  spread(key = "Cluster", value = "value") %>%
  ungroup() %>% 
  mutate(diff = abs(`1` - `2`)) %>%
  group_by(Feature_ID) %>%
  summarise(
    mean_clustDist_L1 = mean(diff, na.rm = TRUE),
    sd_Clust1 = sd(`1`),
    sd_Clust2 = sd(`2`)) %>% 
  filter(
    !is.na(mean_clustDist_L1)
  ) %>% # Some taxa have a single cluster (everyone behaves similarly)
  arrange(-mean_clustDist_L1)
summary(diffBetweenClusts.diet)
  Feature_ID        mean_clustDist_L1   sd_Clust1         sd_Clust2      
 Length:178         Min.   :0.07593   Min.   :0.02711   Min.   :0.03151  
 Class :character   1st Qu.:1.32221   1st Qu.:0.10793   1st Qu.:0.09215  
 Mode  :character   Median :1.82753   Median :0.19367   Median :0.18530  
                    Mean   :1.87661   Mean   :0.25623   Mean   :0.25712  
                    3rd Qu.:2.37622   3rd Qu.:0.33470   3rd Qu.:0.33276  
                    Max.   :5.65328   Max.   :1.49890   Max.   :1.68105  
diffBetweenClusts.diet <- diffBetweenClusts.diet %>%
  filter(sd_Clust1 > median(diffBetweenClusts.diet$sd_Clust1) |
           sd_Clust2 > median(diffBetweenClusts.diet$sd_Clust2))
# Plot top 20
seq_to_plot <- diffBetweenClusts.diet$Feature_ID[1:20]
tax_to_plot <- (TAXTAB %>% column_to_rownames("Seq_ID"))[seq_to_plot, "OrgName"]
tax_to_plot
 [1] "Seq19: Bifidobacterium NA"                "Seq3: Faecalibacterium prausnitzii"      
 [3] "Seq5: Bacteroides vulgatus"               "Seq31: Clostridium_XVIII NA"             
 [5] "Seq29: Bacteroides NA"                    "Seq37: Faecalibacterium prausnitzii"     
 [7] "Seq159: Parasutterella excrementihominis" "Seq22: Bacteroides NA"                   
 [9] "Seq8: Bacteroides dorei"                  "Seq204: Clostridium_XlVa NA"             
[11] "Seq16: Akkermansia muciniphila"           "Seq145: Ruminococcus2 NA"                
[13] "Seq59: Bacteroides NA"                    "Seq79: Blautia NA"                       
[15] "Seq180: NA"                               "Seq101: Phascolarctobacterium faecium"   
[17] "Seq2: Bacteroides vulgatus"               "Seq32: Bacteroides ovatus"               
[19] "Seq24: Dehalobacter NA"                   "Seq131: NA"                              
seq1_subjClusters <- diet_fclust_subjFit %>%
  filter(Feature_ID == "Seq1") %>%
  select(Replicate_ID, Cluster) %>% distinct() %>%
  filter(Cluster==2)
diet2plot.fclust <- diet_fclust_subjFit %>%
  left_join(TAXTAB, by = c("Feature_ID" = "Seq_ID")) %>%
  group_by(Feature_ID) %>%
  mutate(
    n_cluster1 = sum(Cluster == 1),
    n_cluster2 = sum(Cluster == 2),
    majorityCluster = ifelse(n_cluster1 > n_cluster2, 1, 2)
  ) %>% 
  ungroup() %>%
  mutate(
    OrgName = factor(OrgName, levels = tax_to_plot),
    Subject_Cluster = ifelse(Cluster == majorityCluster, "Majority", "Minority")) 
diet2plot <- diet.rsv.subset %>% 
  filter(Seq_ID %in% seq_to_plot) %>%
  left_join(
    diet2plot.fclust %>% 
      select(-value, -time) %>%
      distinct() %>%
      rename(Seq_ID = Feature_ID,  Subject = Replicate_ID) 
  ) %>%
  mutate(
    Seq_ID = factor(Seq_ID, levels = seq_to_plot),
    OrgName = factor(OrgName, levels = tax_to_plot))
Joining, by = c("Seq_ID", "Subject", "Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "OrgName")
Column `OrgName` joining character vector and factor, coercing into character vector
diet2plot.fclust %>%
    filter(Feature_ID %in% seq_to_plot) %>%
ggplot(aes(x = time, y = value, color = Subject_Cluster)) +
  geom_line(
    aes(group = Replicate_ID),
    alpha = 0.3, size = 0.5
  ) +
  geom_smooth(se = FALSE) +
  geom_point(
    data = diet2plot,
    aes(x = Diet_RelDay, y = abundance),
    size = 0.3, alpha = 0.5
  ) +
  geom_vline(xintercept = 0, lwd = 1, color = "orange") +
  geom_vline(xintercept = 4, lwd = 1, color = "orange") +
  geom_line(
    data = diet_fclust_mu %>% 
      filter(Feature_ID %in% seq_to_plot) %>% 
      left_join(TAXTAB, by = c("Feature_ID" = "Seq_ID")) %>%
      mutate(OrgName = factor(OrgName, levels = tax_to_plot)),
    color = "red", size = 1
  ) +
  scale_color_manual(values = c( "grey17", "deepskyblue2")) +
  facet_wrap(~ OrgName, scales = "free", ncol = 4) +
  theme(strip.text = element_text(family="Helvetica-Narrow", size = 10)) +
  scale_x_continuous(name = "Days from diet initiation",
                     breaks = seq(-40, 120, 20), labels =  seq(-40, 120, 20),
                     limits = c(NA, NA)) 

Microbe clusters

Using mean response to Diet

diet_fclust_majority <- diet2plot.fclust %>%
  filter(Subject_Cluster == "Majority") %>%
  left_join(keep_rsv, by = c("Feature_ID" = "Seq_ID")) %>%
  filter(Freq >= min_num_subj) %>%
  select(-Freq) %>%
  group_by(time, Feature_ID) %>%
  summarise(value = mean(value)) %>%
  left_join(TAXTAB, by = c("Feature_ID" = "Seq_ID")) %>%
  arrange( Feature_ID, time)
length(unique(diet_fclust_majority$Feature_ID))
[1] 500
#[1] 500
set.seed(123456)
min_num_subj <- 5
seqClusters.diet <- fit_fpca(
  diet_fclust_majority,
  "time", "value", "Feature_ID",
  cluster = TRUE, K = 6)
seqClusters_fpca.diet <- fitted_values_fpca(seqClusters.diet) %>%
  mutate(Seq_ID = Replicate_ID) 
write.csv(seqClusters_fpca.diet %>% select(Seq_ID, Cluster) %>% distinct() %>%
            left_join(TAXTAB) %>% arrange(Cluster),
          file = "output/diet_seqClusters.csv")
Joining, by = "Seq_ID"
ggplot(
  seqClusters_fpca.diet, aes(x = time, y = value)) +
  geom_line(
    aes(group = Seq_ID, color = Cluster),
    size = 0.7, alpha = 0.5
  ) +
  geom_vline(xintercept = 0, lwd = 1, color = "orange") +
  geom_vline(xintercept = 5, lwd = 1, color = "orange") +
  geom_smooth(color = "grey20") +
  facet_wrap(~ Cluster, labeller = label_both, ncol = 3) +
  scale_color_brewer(palette = "Set1") +
  guides(color = guide_legend(override.aes = list(size=4)))

top_rsv <- seqClusters_fpca.diet %>%
  group_by(Seq_ID, Cluster) %>%
  summarise(mean_abnd = mean(value)) %>%
  arrange(desc(mean_abnd)) %>%
  left_join(TAXTAB %>% select(Seq_ID, OrgName, Species, Genus))
Joining, by = "Seq_ID"
top_genus <- top_rsv %>%
  filter(!is.na(Genus)) %>%
  group_by(Genus, Cluster) %>%
  summarise(
    mean_abnd = mean(mean_abnd),
    prev = n()) %>%
  arrange(Cluster, -prev, -mean_abnd) 
n = 10; nclust = length(unique(top_rsv$Cluster))
df_top_rsv <- top_rsv %>% 
  ungroup() %>%
  select(OrgName, Cluster, mean_abnd) %>%
  group_by(Cluster) %>% 
  top_n(n, wt = mean_abnd) %>%
  arrange(Cluster, mean_abnd) %>%
  select(-mean_abnd) %>% ungroup() %>%
  mutate(Cluster = paste0("Cluster", Cluster)) %>%
  mutate(idx = rep(1:n, nclust)) %>%
  spread(key = Cluster, OrgName)
df_top_rsv %>% select(-idx)

Colon cleanout

bray_to_baseline_fltr %>% 
  filter(perturbation == "CC", RelDay >= -50, RelDay <= 50) %>%
  mutate(Interval = factor(Interval, level = names(cc_intv_cols))) %>%
  ggplot(
    aes(x = RelDay, y = dist_to_baseline, 
        group = Subject, color = Interval)) +
  geom_line(aes(group = Subject), alpha = 0.7, lwd = 0.5) + 
  geom_point(alpha = 0.5, size = 1.2) + 
  scale_color_manual(values = cc_intv_cols) + 
  theme(legend.position = "bottom") + 
  guides(colour = guide_legend(override.aes = list(size=3))) +
  xlab("Days from colon cleanout") +
  ylab("Bray-Curtis distance to 7 pre-diet samples") 

fpca.bray.cc30 <- fit_dist_to_baseline(
  bray_to_baseline_fltr %>% 
    filter(perturbation == "CC", RelDay >= -30, RelDay <= 30) %>%
    arrange(Subject, RelDay))

save(list = c("fpca.bray.abx", "fpca.bray.diet","fpca.bray.diet30", 
              "fpca.bray.cc", "fpca.bray.cc30"), 
     file = "output/fpca_res.rda")
(pCC <- bray_to_baseline_fltr %>% 
  filter(perturbation == "CC", RelDay >= -30, RelDay <= 30) %>%
ggplot(aes(x = RelDay, y = dist_to_baseline)) +
  geom_line(
    data = fpca.bray.cc30[["fitted"]],
    aes(group = Subject, x = time, y = value),
    alpha = 0.3, size = 0.7, color = "grey30") +
  geom_vline(xintercept = 0, lwd = 1, color = "orange") +
  geom_vline(xintercept = 4, lwd = 1, color = "orange") +
  geom_point(aes(color = Interval), size = 1.5, alpha = 0.7) +
  geom_line(
    data = fpca.bray.cc30[["mean"]], aes(x = time, y = value),
    color = "navy", size = 2) +
  scale_color_manual(values = cc_intv_cols, name = "Interval") +
  scale_x_continuous(
    name = "Days from colon cleanout",
    limits = c(NA, NA), breaks = seq(-50, 60, 10)) +
  scale_y_continuous(
    name = "Bray-Curtis distance to baseline", 
    limits = c(NA, NA), breaks = seq(0.1, 0.80, 0.1)) +
  theme(text = element_text(size = 20))) 

fpca.bray.cc30[["fitted"]] <- fpca.bray.cc30[["fitted"]] %>%
  mutate(Interval = ifelse(fpca.bray.cc30[["fitted"]]$time < 0 , "PreCC","PostCC"))
(pCCDeriv <-  fpca.bray.cc30[["fitted"]] %>%
  ggplot(aes(x = RelDay)) +
  geom_line(
    aes(group = Subject, x = time, y = deriv, color = Interval),
    alpha = 0.5, size = 0.7) +
  geom_vline(xintercept = 0, lwd = 1, color = "orange") +
  geom_vline(xintercept = 4, lwd = 1, color = "orange") +
  scale_color_manual(values = cc_intv_cols, name = "Interval") +
  scale_x_continuous(name = "",
    limits = c(NA, NA), breaks = seq(-50, 60, 10)) +
  ylab("Derivative"))

(pCCLab <- bray_to_baseline_fltr %>% 
  filter(perturbation == "CC", RelDay >= -50, RelDay <= 50) %>%
ggplot(aes(x = RelDay, y = dist_to_baseline)) +
  geom_line(
    data = fpca.bray.cc[["fitted"]],
    aes(group = Subject, x = time, y = value),
    alpha = 0.3, size = 0.7 ) +
  geom_vline(xintercept = 0, lwd = 1, color = "orange") +
  geom_vline(xintercept = 4, lwd = 1, color = "orange") +
  geom_text(
      aes(label = Subject, color = Interval), size = 4, alpha = 0.7) +
  geom_line(
    data = fpca.bray.cc[["mean"]], aes(x = time, y = value),
    color = "navy", size = 2) +
  scale_color_manual(values = cc_intv_cols, name = "Interval") +
  scale_x_continuous(
    name = "Days from colon cleanout",
    limits = c(NA, NA), breaks = seq(-50, 60, 10)) +
  scale_y_continuous(
    name = "Bray-Curtis distance to baseline", 
    limits = c(0.1, 0.85), breaks = seq(0.1, 0.80, 0.1)) +
  theme(text = element_text(size = 20)))

vp = grid::viewport(width = 0.3, height = 0.32, x = 0.07, y =0.95, just = c("left", "top"))
print(pCC)
print(pCCDeriv + theme_bw(base_size = 10) + theme_subplot, vp = vp)

sessionInfo()
R version 3.5.1 (2018-07-02)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: CentOS Linux 7 (Core)

Matrix products: default
BLAS/LAPACK: /share/software/user/open/openblas/0.2.19/lib/libopenblasp-r0.2.19.so

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8       
 [4] LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C              
[10] LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] fdapace_0.4.1        forcats_0.4.0        stringr_1.4.0        dplyr_0.8.3          purrr_0.3.2         
 [6] readr_1.3.1          tidyr_0.8.3          tibble_2.1.3         ggplot2_3.2.0        tidyverse_1.2.1.9000
[11] RColorBrewer_1.1-2   phyloseq_1.26.1     

loaded via a namespace (and not attached):
 [1] nlme_3.1-140        fs_1.3.1            lubridate_1.7.4     httr_1.4.0          numDeriv_2016.8-1.1
 [6] tools_3.5.1         backports_1.1.4     R6_2.4.0            vegan_2.5-5         rpart_4.1-15       
[11] Hmisc_4.2-0         DBI_1.0.0           lazyeval_0.2.2      BiocGenerics_0.28.0 mgcv_1.8-28        
[16] colorspace_1.4-1    nnet_7.3-12         permute_0.9-5       ade4_1.7-13         withr_2.1.2        
[21] gridExtra_2.3       tidyselect_0.2.5    compiler_3.5.1      cli_1.1.0           rvest_0.3.4        
[26] Biobase_2.42.0      htmlTable_1.13.1    xml2_1.2.0          labeling_0.3        checkmate_1.9.4    
[31] scales_1.0.0        digest_0.6.20       foreign_0.8-71      rmarkdown_1.14      XVector_0.22.0     
[36] base64enc_0.1-3     pkgconfig_2.0.2     htmltools_0.3.6     dbplyr_1.4.2        htmlwidgets_1.3    
[41] rlang_0.4.0         readxl_1.3.1        rstudioapi_0.10     generics_0.0.2      jsonlite_1.6       
[46] acepack_1.4.1       magrittr_1.5        Formula_1.2-3       biomformat_1.10.1   Matrix_1.2-17      
[51] Rcpp_1.0.1          munsell_0.5.0       S4Vectors_0.20.1    Rhdf5lib_1.4.3      ape_5.3            
[56] stringi_1.4.3       yaml_2.2.0          MASS_7.3-51.4       zlibbioc_1.28.0     rhdf5_2.26.2       
[61] plyr_1.8.4          grid_3.5.1          parallel_3.5.1      crayon_1.3.4        lattice_0.20-38    
[66] Biostrings_2.50.2   haven_2.1.1         splines_3.5.1       multtest_2.38.0     hms_0.5.0          
[71] zeallot_0.1.0       knitr_1.23          pillar_1.4.2        igraph_1.2.4.1      reshape2_1.4.3     
[76] codetools_0.2-16    stats4_3.5.1        reprex_0.3.0        glue_1.3.1          evaluate_0.14      
[81] latticeExtra_0.6-28 data.table_1.12.2   modelr_0.1.4        vctrs_0.2.0         foreach_1.4.4      
[86] cellranger_1.1.0    gtable_0.3.0        assertthat_0.2.1    xfun_0.8            broom_0.5.2        
[91] pracma_2.2.5        survival_2.44-1.1   iterators_1.0.10    IRanges_2.16.0      cluster_2.1.0      
LS0tCnRpdGxlOiAiRnVuY3Rpb25hbCBQQ0EiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSJhc2lzIn0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KAogIG1lc3NhZ2UgPSBGQUxTRSwgZXJyb3IgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLCAKICBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gNiwKICBmaWcucGF0aCA9ICIuL2ZpZ3MvZnVuY3Rpb25hbF9QQ0EvIiwgCiAgZGV2PSdwbmcnKSAKYGBgCgoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKbGlicmFyeSgicGh5bG9zZXEiKQpsaWJyYXJ5KCJSQ29sb3JCcmV3ZXIiKQpsaWJyYXJ5KCJ0aWR5dmVyc2UiKQpsaWJyYXJ5KCJmZGFwYWNlIikKCmRhdGFkaXIgPC0gIi4uLy4uL2RhdGEvIgpjdXJkaXIgPC0gZ2V0d2QoKQp0aGVtZV9zZXQodGhlbWVfYncoKSkKdGhlbWVfdXBkYXRlKHRleHQgPSBlbGVtZW50X3RleHQoMjApKQoKdGhlbWVfc3VicGxvdCA8LSB0aGVtZSgKICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50Iixjb2xvdXIgPSBOQSksCiAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50Iixjb2xvdXIgPSBOQSkKICAgICkKCmFieF9pbnR2X2NvbHMgPC0gYygiUHJlQWJ4IiA9ICJncmV5NjAiLCAiTWlkQWJ4IiA9ICIjRTQxQTFDIiwgCiAgICAgICAgICAgICAgICAgICAiUG9zdEFieCIgPSAiIzAwQkZDNCIsICJVbnBBYngiID0gIlB1cnBsZSIpCmRpZXRfaW50dl9jb2xzIDwtIGMoIlByZURpZXQiID0gImdyZXk2MCIsICJNaWREaWV0IiA9ICIjRkQ4RDNDIiwgCiAgICAgICAgICAgICAgICAgICAgIlBvc3REaWV0IiA9ICIjNERBRjRBIikKY2NfaW50dl9jb2xzIDwtIGMoIlByZUNDIiA9ICJncmV5NjAiLCAiUG9zdENDIiA9ICIjN0EwMTc3IikgIyIjQUUwMTdFIikKCmludHZfY29scyA8LSBjKGFieF9pbnR2X2NvbHMsIGRpZXRfaW50dl9jb2xzLCBjY19pbnR2X2NvbHMsICJOb0ludGVydiIgPSAiZ3JleTYwIikKYGBgCgoKCiMgTG9hZCBEYXRhCgpgYGB7cn0KIyBGaWxlIGdlbmVyYXRlZCBpbiAvcGVydHVyYmF0aW9uXzE2cy9hbmFseXNpcy9hbmFseXNpc19zdW1tZXIyMDE5L2dlbmVyYXRlX3BoeWxvc2VxLnJtZApwc1N1YmogPC0gcmVhZFJEUygiLi4vLi4vZGF0YS8xNlMvcGh5bG9zZXEvcGVydHVyYl9waHlzZXFfcGFydGljaXBhbnRzX2RlY29udGFtXzE1SnVsMTkucmRzIikKcHNTdWJqCiMgb3R1X3RhYmxlKCkgICBPVFUgVGFibGU6ICAgICAgICAgWyAyNDI1IHRheGEgYW5kIDQ0MDIgc2FtcGxlcyBdCiMgc2FtcGxlX2RhdGEoKSBTYW1wbGUgRGF0YTogICAgICAgWyA0NDAyIHNhbXBsZXMgYnkgNDAgc2FtcGxlIHZhcmlhYmxlcyBdClNNUCA8LSBkYXRhLmZyYW1lKHNhbXBsZV9kYXRhKHBzU3ViaikpClNVQkogPC0gU01QICU+JSBzZWxlY3QoU3ViamVjdCwgQWdlOkJpcnRoWWVhcikgJT4lIGRpc3RpbmN0KCkKClRBWFRBQiA8LSBkYXRhLmZyYW1lKGFzKHRheF90YWJsZShwc1N1YmopLCAibWF0cml4IikpICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigiU2VxX0lEIikgJT4lCiAgc2VsZWN0KC1TZXEpICU+JQogIG11dGF0ZSgKICAgIE9yZ05hbWUuUkRQID0gcGFzdGUoR2VudXMsIFNwZWNpZXMpLAogICAgT3JnTmFtZS5TaWx2YSA9IHBhc3RlKEdlbnVzU2lsdmEsIFNwZWNpZXNTaWx2YSksCiAgICBPcmdOYW1lID0gaWZlbHNlKGdyZXBsKCJOQSIsIE9yZ05hbWUuUkRQKSAmICFncmVwbCgiTkEiLCBPcmdOYW1lLlJEUCksCiAgICAgICAgICAgICAgICAgICAgIE9yZ05hbWUuU2lsdmEsIE9yZ05hbWUuUkRQKSwKICAgIE9yZ05hbWUgPSBpZmVsc2UoT3JnTmFtZSA9PSAiTkEgTkEiLCAiTkEiLCBPcmdOYW1lKSwKICAgIE9yZ05hbWUgPSBwYXN0ZTAoU2VxX0lELCAiOiAiLCBPcmdOYW1lKSkgJT4lCiAgc2VsZWN0KDE6OCwgT3JnTmFtZSkgCmhlYWQoVEFYVEFCKQpgYGAKCgpgYGB7cn0KbG9hZCgib3V0cHV0L3BhaXJ3aXNlX2Rpc3RfdG9fYmFzZWxpbmVfc3Vial8xNlMucmRhIikKCmxvYWQoIm91dHB1dC9mcGNhX3Jlcy5yZGEiKQpscygpCmBgYAoKCiMgRnVuY3Rpb25hbCBQQ0EgCgoKYGBge3J9CnNvdXJjZSgiLi9mcGNhX2Z1bnMuUiIpCmBgYAoKCmBgYHtyfQojIHRoaXMgaXMgYmVjYXVzZSBzb21lIHN1YmplY3RzIGhhdmUgbXVsdGlwbGUgc2FtcGxlcyB0YWtlIHRoZSBzYW1lIGRheQpicmF5X3RvX2Jhc2VsaW5lX2ZsdHIgPC0gYnJheV90b19iYXNlbGluZSAlPiUgCiAgZ3JvdXBfYnkocGVydHVyYmF0aW9uLCBTdWJqZWN0LCBHcm91cCwgSW50ZXJ2YWwsIFJlbERheSkgJT4lCiAgc3VtbWFyaXNlKG4gPSBuKCksIGRpc3RfdG9fYmFzZWxpbmUgPSBtZWFuKGRpc3RfdG9fYmFzZWxpbmUpKSAlPiUKICB1bmdyb3VwKCkKCmJyYXlfdG9fYmFzZWxpbmVfZmx0ciAlPiUgZmlsdGVyKG4gPiAxKQpgYGAKCgojIyBBbnRpYmlvdGljcwoKCmBgYHtyIGJyYXktYWJ4fQpicmF5X3RvX2Jhc2VsaW5lX2ZsdHIgJT4lIAogIGZpbHRlcihwZXJ0dXJiYXRpb24gPT0gIkFieCIpICU+JQogIGZpbHRlcihSZWxEYXkgPj0gLTUwLCBSZWxEYXkgPD0gNjApICU+JQogIG11dGF0ZShJbnRlcnZhbCA9IGZhY3RvcihJbnRlcnZhbCwgbGV2ZWwgPSBuYW1lcyhhYnhfaW50dl9jb2xzKSkpICU+JQogIGdncGxvdCgKICAgIGFlcyh4ID0gUmVsRGF5LCB5ID0gZGlzdF90b19iYXNlbGluZSwgCiAgICAgICAgZ3JvdXAgPSBTdWJqZWN0LCBjb2xvciA9IEludGVydmFsKSkgKwogIGdlb21fbGluZShhZXMoZ3JvdXAgPSBTdWJqZWN0KSwgYWxwaGEgPSAwLjcsIGx3ZCA9IDAuNSkgKyAKICBnZW9tX3BvaW50KGFscGhhID0gMC41LCBzaXplID0gMS4yKSArIAogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBhYnhfaW50dl9jb2xzKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSArIAogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTMpKSkgCiMgKwojICAgeGxhYigiRGF5cyBmcm9tIGluaXRpYWwgYW50aWJpb3RpYyBkb3NlIikgKwojICAgeWxhYigiQnJheS1DdXJ0aXMgZGlzdGFuY2UgdG8gNyBwcmUtYW50aWJpb3RpYyBzYW1wbGVzIikgCmBgYAoKCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpmcGNhLmJyYXkuYWJ4IDwtIGZpdF9kaXN0X3RvX2Jhc2VsaW5lKAogIGJyYXlfdG9fYmFzZWxpbmVfZmx0ciAlPiUgZmlsdGVyKAogICAgcGVydHVyYmF0aW9uID09ICJBYngiLCBSZWxEYXkgPj0gLTUwLCBSZWxEYXkgPD0gNjApKQoKc2F2ZShsaXN0ID0gYygiZnBjYS5icmF5LmFieCIpLCBmaWxlID0gIm91dHB1dC9mcGNhX3Jlcy5yZGEiKQpgYGAKCmBgYHtyIGZwY2EtYnJheS1hYngsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD02LjV9CihwQWJ4IDwtIGJyYXlfdG9fYmFzZWxpbmVfZmx0ciAlPiUgCiAgZmlsdGVyKHBlcnR1cmJhdGlvbiA9PSAiQWJ4IiwgUmVsRGF5ID49IC01MCwgUmVsRGF5IDw9IDYwKSAlPiUKZ2dwbG90KGFlcyh4ID0gUmVsRGF5LCB5ID0gZGlzdF90b19iYXNlbGluZSkpICsKICBnZW9tX2xpbmUoCiAgICBkYXRhID0gZnBjYS5icmF5LmFieFtbImZpdHRlZCJdXSwKICAgIGFlcyhncm91cCA9IFN1YmplY3QsIHggPSB0aW1lLCB5ID0gdmFsdWUpLAogICAgYWxwaGEgPSAwLjMsIHNpemUgPSAwLjcsIGNvbG9yID0gImdyZXkzMCIpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBsd2QgPSAxLCBjb2xvciA9ICJvcmFuZ2UiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gNCwgbHdkID0gMSwgY29sb3IgPSAib3JhbmdlIikgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gSW50ZXJ2YWwpLCBzaXplID0gMS41LCBhbHBoYSA9IDAuNykgKwogIGdlb21fbGluZSgKICAgIGRhdGEgPSBmcGNhLmJyYXkuYWJ4W1sibWVhbiJdXSwgYWVzKHggPSB0aW1lLCB5ID0gdmFsdWUpLAogICAgY29sb3IgPSAibmF2eSIsIHNpemUgPSAyKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGFieF9pbnR2X2NvbHMsIG5hbWUgPSAiSW50ZXJ2YWwiKSArCiAgc2NhbGVfeF9jb250aW51b3VzKAogICAgbmFtZSA9ICJEYXlzIGZyb20gaW5pdGlhbCBhbnRpYmlvdGljIGRvc2UiLAogICAgbGltaXRzID0gYyhOQSwgTkEpLCBicmVha3MgPSBzZXEoLTUwLCA2MCwgMTApKSArCiAgc2NhbGVfeV9jb250aW51b3VzKAogICAgbmFtZSA9ICJCcmF5LUN1cnRpcyBkaXN0YW5jZSB0byBiYXNlbGluZSIsIAogICAgbGltaXRzID0gYygwLjEsIDAuODUpLCBicmVha3MgPSBzZXEoMC4xLCAwLjgwLCAwLjEpKSArCiAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMjApKSkKYGBgCgoKYGBge3IgZnBjYS1kZXJpdi1icmF5LWFieCwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTYuNX0KZnBjYS5icmF5LmFieFtbImZpdHRlZCJdXSA8LSBmcGNhLmJyYXkuYWJ4W1siZml0dGVkIl1dICU+JQogIG11dGF0ZSgKICAgIEludGVydmFsID0gaWZlbHNlKGZwY2EuYnJheS5hYnhbWyJmaXR0ZWQiXV0kdGltZSA8IDAgLCAiUHJlQWJ4IiwKICAgICAgICAgICAgICAgaWZlbHNlKGZwY2EuYnJheS5hYnhbWyJmaXR0ZWQiXV0kdGltZSA+PSAwICYgZnBjYS5icmF5LmFieFtbImZpdHRlZCJdXSR0aW1lIDw9IDQsICJNaWRBYngiLAogICAgICAgICAgICAgICAgICAgICAgIlBvc3RBYngiKSkpCgoocEFieERlcml2IDwtICBmcGNhLmJyYXkuYWJ4W1siZml0dGVkIl1dICU+JQogIGdncGxvdChhZXMoeCA9IFJlbERheSkpICsKICBnZW9tX2xpbmUoCiAgICBhZXMoZ3JvdXAgPSBTdWJqZWN0LCB4ID0gdGltZSwgeSA9IGRlcml2LCBjb2xvciA9IEludGVydmFsKSwKICAgIGFscGhhID0gMC41LCBzaXplID0gMC43KSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgbHdkID0gMSwgY29sb3IgPSAib3JhbmdlIikgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDQsIGx3ZCA9IDEsIGNvbG9yID0gIm9yYW5nZSIpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYWJ4X2ludHZfY29scywgbmFtZSA9ICJJbnRlcnZhbCIpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobmFtZSA9ICIiLAogICAgbGltaXRzID0gYyhOQSwgTkEpLCBicmVha3MgPSBzZXEoLTUwLCA2MCwgMTApKSArCiAgeWxhYigiRGVyaXZhdGl2ZSIpKQpgYGAKCgpgYGB7ciBmcGNhLWJyYXktYWJ4LWxhYnMsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD02LjV9CihwQWJ4TGFiIDwtIGJyYXlfdG9fYmFzZWxpbmVfZmx0ciAlPiUgCiAgZmlsdGVyKHBlcnR1cmJhdGlvbiA9PSAiQWJ4IiwgUmVsRGF5ID49IC01MCwgUmVsRGF5IDw9IDYwKSAlPiUKZ2dwbG90KGFlcyh4ID0gUmVsRGF5LCB5ID0gZGlzdF90b19iYXNlbGluZSkpICsKICBnZW9tX2xpbmUoCiAgICBkYXRhID0gZnBjYS5icmF5LmFieFtbImZpdHRlZCJdXSwKICAgIGFlcyhncm91cCA9IFN1YmplY3QsIHggPSB0aW1lLCB5ID0gdmFsdWUpLAogICAgYWxwaGEgPSAwLjMsIHNpemUgPSAwLjcgKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgbHdkID0gMSwgY29sb3IgPSAib3JhbmdlIikgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDQsIGx3ZCA9IDEsIGNvbG9yID0gIm9yYW5nZSIpICsKICBnZW9tX3RleHQoCiAgICAgIGFlcyhsYWJlbCA9IFN1YmplY3QsIGNvbG9yID0gSW50ZXJ2YWwpLCBzaXplID0gNCwgYWxwaGEgPSAwLjcpICsKICBnZW9tX2xpbmUoCiAgICBkYXRhID0gZnBjYS5icmF5LmFieFtbIm1lYW4iXV0sIGFlcyh4ID0gdGltZSwgeSA9IHZhbHVlKSwKICAgIGNvbG9yID0gIm5hdnkiLCBzaXplID0gMikgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBhYnhfaW50dl9jb2xzLCBuYW1lID0gIkludGVydmFsIikgKwogIHNjYWxlX3hfY29udGludW91cygKICAgIG5hbWUgPSAiRGF5cyBmcm9tIGluaXRpYWwgYW50aWJpb3RpYyBkb3NlIiwKICAgIGxpbWl0cyA9IGMoTkEsIE5BKSwgYnJlYWtzID0gc2VxKC01MCwgNjAsIDEwKSkgKwogIHNjYWxlX3lfY29udGludW91cygKICAgIG5hbWUgPSAiQnJheS1DdXJ0aXMgZGlzdGFuY2UgdG8gYmFzZWxpbmUiLCAKICAgIGxpbWl0cyA9IGMoMC4xLCAwLjg1KSwgYnJlYWtzID0gc2VxKDAuMSwgMC44MCwgMC4xKSkgKwogIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIwKSkpCgogICMgZ2VvbV9wb2ludCgKICAjICAgICBkYXRhID0gYWJ4X2JyYXkgJT4lIGZpbHRlcihSZWxEYXkgPiBTdGFiVGltZSksCiAgIyAgICAgY29sb3IgPSAib3JhbmdlIiwgc2l6ZSA9IDIuNSkgKwpgYGAKCmBgYHtyIGZwY2EtYnJheS1hYngtdmFsLWRlcml2LCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9Ni41fQp2cCA9IGdyaWQ6OnZpZXdwb3J0KHdpZHRoID0gMC4zLCBoZWlnaHQgPSAwLjMyLCB4ID0gMC4wNywgeSA9MC45NSwganVzdCA9IGMoImxlZnQiLCAidG9wIikpCnByaW50KHBBYngpCnByaW50KHBBYnhEZXJpdiArIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDEwKSArIHRoZW1lX3N1YnBsb3QsIHZwID0gdnApCmBgYAoKCiMjIyBDbHVzdGVyIFJTVnMKCmBgYHtyLCBldmFsID0gRkFMU0V9CmFieEZhYyA8LSBjKCJQcmVBYngiLCAiTWlkQWJ4IiwgIlBvc3RBYngiKQphYnggPC0gc3Vic2V0X3NhbXBsZXMocHNTdWJqLCBBYnhfUmVsRGF5ID49IC01MCAmIEFieF9SZWxEYXkgPD0gNjApCmFieCA8LSBzdWJzZXRfdGF4YShhYngsIHRheGFfc3VtcyhhYngpID4gMCkKYWJ4CgoocmVwbGljYXRlZCA8LSBkYXRhLmZyYW1lKHNhbXBsZV9kYXRhKGFieCkpICU+JSAKICBncm91cF9ieShTdWJqZWN0LCBHcm91cCwgSW50ZXJ2YWwsIEFieF9SZWxEYXkpICU+JQogIG11dGF0ZShuID0gbigpKSAlPiUKICB1bmdyb3VwKCkgJT4lIGZpbHRlcihuID4gMSkpCmBgYAoKYGBge3IsIGV2YWwgPSBGQUxTRX0KIyBSZW1vdmUgcmVwbGljYXRlZCBzYW1wbGVzCnNhbXBsZV9zdW1zKGFieClbcmVwbGljYXRlZCRNZWFzX0lEXQoKIyBNNzg2OSBNNzg4NiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiMgNzQ2OTcgNzQ4NzMKCiMgd2UgcmV0YWluIGEgcmVwbGljYXRlIHdpdGggaGlnaGVyIHNhbXBsZSBkZXB0aDoKCmFieCA8LSBzdWJzZXRfc2FtcGxlcyhhYngsICFNZWFzX0lEICVpbiUgYygiTTIyNDMiLCAiTTc4NjkiKSkKYWJ4CiNvdHVfdGFibGUoKSAgIE9UVSBUYWJsZTogICAgICAgICBbIDIzMzkgdGF4YSBhbmQgMTQxOCBzYW1wbGVzIF0gCmBgYAoKCmBgYHtyLCBldmFsID0gRkFMU0V9CiMgQXNpbmggdGhlIGRhdGEgc28gdGhhdCB0aGUgY291bnRzIGFyZSBjb21wYXJhYmxlCmFieC5yc3YgPC0gZGF0YS5mcmFtZShhc2luaChhcyhvdHVfdGFibGUoYWJ4KSwgIm1hdHJpeCIpKSkgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCJTZXFfSUQiKSAlPiUKICBnYXRoZXIoTWVhc19JRCwgYWJ1bmRhbmNlLCAtU2VxX0lEKSAlPiUKICBsZWZ0X2pvaW4oU01QKSAlPiUKICBsZWZ0X2pvaW4oVEFYVEFCKSAlPiUKICBtdXRhdGUoQWJ4X0ludGVydmFsID0gZmFjdG9yKEFieF9JbnRlcnZhbCwgbGV2ZWxzID0gYWJ4RmFjKSkgJT4lCiAgYXJyYW5nZShTZXFfSUQsIFN1YmplY3QsIEFieF9SZWxEYXkpCgoKdGhyZXNoIDwtIDA7IG51bV9ub256ZXJvIDwtIDEwOyAKbWluX25vX3N1YmogPC0gMwoKa2VlcF9yc3YgPC0gYWJ4LnJzdiAlPiUKICBmaWx0ZXIoYWJ1bmRhbmNlID4gdGhyZXNoKSAlPiUKICBncm91cF9ieShTdWJqZWN0LCBTZXFfSUQpICU+JQogIHN1bW1hcmlzZShGcmVxID0gbigpKSAlPiUgICAjIE5vLiBvZiBzYW1wbGVzIHBlciBzdWJqZWN0IHcvIHBvc2l0aXZlIHJzdiBjb3VudCAKICBmaWx0ZXIoRnJlcSA+PSBudW1fbm9uemVybykgJT4lCiAgZ3JvdXBfYnkoU2VxX0lEKSAlPiUKICBzdW1tYXJpc2UoRnJlcSA9IG4oKSkgJT4lICMgTnVtYmVyIG9mIHN1YmplY3RzIHcvIG51bV9ub256ZXJvIHNhbXBsZXMgd2l0aCBjb3VudHMgPiB0aHJlc2gKICBmaWx0ZXIoRnJlcSA+PSBtaW5fbm9fc3ViaikgJT4lCiAgYXJyYW5nZShkZXNjKEZyZXEpKQoKbGVuZ3RoKHVuaXF1ZShrZWVwX3JzdiRTZXFfSUQpKSAjID0gODM5CgphYngucnN2LnN1YnNldCA8LSBhYngucnN2ICU+JQogIGZpbHRlcihTZXFfSUQgJWluJSB1bmlxdWUoa2VlcF9yc3YkU2VxX0lEKSkKCiNzYXZlKGxpc3QgPSBjKCJrZWVwX3JzdiIsICJhYngucnN2LnN1YnNldCIpLCBmaWxlID0gInJlc3VsdHMvZnBjYV9jbHVzdF9yZXNfYWJ4LnJkYSIpCmBgYAoKCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQphYnhfZmNsdXN0IDwtIGZwY2Ffd3JhcHBlcigKICBhYngucnN2LnN1YnNldCwKICB0aW1lX2NvbHVtbiA9ICJBYnhfUmVsRGF5IiwKICB2YWx1ZV9jb2x1bW4gPSAiYWJ1bmRhbmNlIiwKICByZXBsaWNhdGVfY29sdW1uID0gIlN1YmplY3QiLAogIGZlYXRfY29sdW1uID0gIlNlcV9JRCIsCiAgY2x1c3RlciA9IFRSVUUsCiAgY2x1c3RfbWluX251bV9yZXBsaWNhdGUgPSAxNSwKICBmcGNhX29wdG5zID0gTlVMTCwgZmNsdXN0X29wdG5zID0gTlVMTCwKICBwYXJhbGxlbCA9IFRSVUUsIG5jb3JlcyA9IDE2KQpzYXZlKGxpc3QgPSBjKCJhYnhfZmNsdXN0IiksCiAgICBmaWxlID0gInJlc3VsdHMvYWJ4X3Jzdl9mY2x1c3QucmRhIikKCmFieF9mY2x1c3RfbXUgPC0gZ2V0X2ZwY2FfbWVhbnMoYWJ4X2ZjbHVzdCkKYWJ4X2ZjbHVzdF9zdWJqRml0IDwtIGdldF9mcGNhX2ZpdHMoYWJ4X2ZjbHVzdCkKCnNhdmUobGlzdCA9IGMoImtlZXBfcnN2IiwgImFieC5yc3Yuc3Vic2V0IiwgCiAgICAgICAgICAgICAgImFieF9mY2x1c3QiLCAiYWJ4X2ZjbHVzdF9zdWJqRml0IiwgImFieF9mY2x1c3RfbXUiKSwKICAgIGZpbGUgPSAib3V0cHV0L2ZwY2FfY2x1c3RfcmVzX2FieC5yZGEiKQpgYGAKCldlIG5vdyBmaW5kIHRoZSB0YXhhIHdpdGggdGhlIG1vc3QgdmFyaWFiaWxpdHkgaW4gdGhlIHJlc3BvbnNlIHRvIEFCWCBiZXR3ZWVuIHR3byBjbHVzdGVyczoKCmBgYHtyfQojIFRheGEncyB0cmFqZWN0b3J5IGRpZmZlcmVuY2UgYmV0d2VlbiB0d28gY2x1c3RlcnMKbG9hZCgib3V0cHV0L2ZwY2FfY2x1c3RfcmVzX2FieC5yZGEiKQoKI2xvYWQoIm91dHB1dC9mcGNhX2NsdXN0X3Jlc19kaWV0LnJkYSIpCmRpZmZCZXR3ZWVuQ2x1c3RzLmFieCA8LSBhYnhfZmNsdXN0X3N1YmpGaXQgJT4lCiAgZ3JvdXBfYnkoRmVhdHVyZV9JRCwgdGltZSwgQ2x1c3RlciApICU+JSAKICBzdW1tYXJpc2UodmFsdWUgPSBtZWFuKHZhbHVlLCBuYS5ybSA9IFRSVUUpKSAlPiUKICBzcHJlYWQoa2V5ID0gIkNsdXN0ZXIiLCB2YWx1ZSA9ICJ2YWx1ZSIpICU+JQogIHVuZ3JvdXAoKSAlPiUgCiAgbXV0YXRlKGRpZmYgPSBhYnMoYDFgIC0gYDJgKSkgJT4lCiAgZ3JvdXBfYnkoRmVhdHVyZV9JRCkgJT4lCiAgc3VtbWFyaXNlKAogICAgbWVhbl9jbHVzdERpc3RfTDEgPSBtZWFuKGRpZmYsIG5hLnJtID0gVFJVRSksCiAgICBzZF9DbHVzdDEgPSBzZChgMWApLAogICAgc2RfQ2x1c3QyID0gc2QoYDJgKSkgJT4lIAogIGZpbHRlcigKICAgICFpcy5uYShtZWFuX2NsdXN0RGlzdF9MMSkKICApICU+JSAjIFNvbWUgdGF4YSBoYXZlIGEgc2luZ2xlIGNsdXN0ZXIgKGV2ZXJ5b25lIGJlaGF2ZXMgc2ltaWxhcmx5KQogIGFycmFuZ2UoLW1lYW5fY2x1c3REaXN0X0wxKQoKc3VtbWFyeShkaWZmQmV0d2VlbkNsdXN0cy5hYngpCgpkaWZmQmV0d2VlbkNsdXN0cy5hYnggPC0gZGlmZkJldHdlZW5DbHVzdHMuYWJ4ICU+JQogIGZpbHRlcihzZF9DbHVzdDEgPiBtZWRpYW4oZGlmZkJldHdlZW5DbHVzdHMuYWJ4JHNkX0NsdXN0MSkgfAogICAgICAgICAgIHNkX0NsdXN0MiA+IG1lZGlhbihkaWZmQmV0d2VlbkNsdXN0cy5hYngkc2RfQ2x1c3QyKSkKYGBgCgoKYGBge3J9CiMgUGxvdCB0b3AgMjAKc2VxX3RvX3Bsb3QgPC0gZGlmZkJldHdlZW5DbHVzdHMuYWJ4JEZlYXR1cmVfSURbMToyMF0KdGF4X3RvX3Bsb3QgPC0gKFRBWFRBQiAlPiUgY29sdW1uX3RvX3Jvd25hbWVzKCJTZXFfSUQiKSlbc2VxX3RvX3Bsb3QsICJPcmdOYW1lIl0KdGF4X3RvX3Bsb3QKCgpzZXExX3N1YmpDbHVzdGVycyA8LSBhYnhfZmNsdXN0X3N1YmpGaXQgJT4lCiAgZmlsdGVyKEZlYXR1cmVfSUQgPT0gIlNlcTEiKSAlPiUKICBzZWxlY3QoUmVwbGljYXRlX0lELCBDbHVzdGVyKSAlPiUgZGlzdGluY3QoKSAlPiUKICBmaWx0ZXIoQ2x1c3Rlcj09MikKCmFieDJwbG90LmZjbHVzdCA8LSBhYnhfZmNsdXN0X3N1YmpGaXQgJT4lCiAgbGVmdF9qb2luKFRBWFRBQiwgYnkgPSBjKCJGZWF0dXJlX0lEIiA9ICJTZXFfSUQiKSkgJT4lCiAgZ3JvdXBfYnkoRmVhdHVyZV9JRCkgJT4lCiAgbXV0YXRlKAogICAgbl9jbHVzdGVyMSA9IHN1bShDbHVzdGVyID09IDEpLAogICAgbl9jbHVzdGVyMiA9IHN1bShDbHVzdGVyID09IDIpLAogICAgbWFqb3JpdHlDbHVzdGVyID0gaWZlbHNlKG5fY2x1c3RlcjEgPiBuX2NsdXN0ZXIyLCAxLCAyKQogICkgJT4lIAogIHVuZ3JvdXAoKSAlPiUKICBtdXRhdGUoCiAgICBPcmdOYW1lID0gZmFjdG9yKE9yZ05hbWUsIGxldmVscyA9IHRheF90b19wbG90KSwKICAgIFN1YmplY3RfQ2x1c3RlciA9IGlmZWxzZShDbHVzdGVyID09IG1ham9yaXR5Q2x1c3RlciwgIk1ham9yaXR5IiwgIk1pbm9yaXR5IikpIAoKCmFieDJwbG90IDwtIGFieC5yc3Yuc3Vic2V0ICU+JSAKICBmaWx0ZXIoU2VxX0lEICVpbiUgc2VxX3RvX3Bsb3QpICU+JQogIGxlZnRfam9pbigKICAgIGFieDJwbG90LmZjbHVzdCAlPiUgCiAgICAgIHNlbGVjdCgtdmFsdWUsIC10aW1lKSAlPiUKICAgICAgZGlzdGluY3QoKSAlPiUKICAgICAgcmVuYW1lKFNlcV9JRCA9IEZlYXR1cmVfSUQsICBTdWJqZWN0ID0gUmVwbGljYXRlX0lEKSAKICApICU+JQogIG11dGF0ZSgKICAgIFNlcV9JRCA9IGZhY3RvcihTZXFfSUQsIGxldmVscyA9IHNlcV90b19wbG90KSwKICAgIE9yZ05hbWUgPSBmYWN0b3IoT3JnTmFtZSwgbGV2ZWxzID0gdGF4X3RvX3Bsb3QpKQpgYGAKCgoKYGBge3IgZGlmZlRheGEtYWJ4LCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9MTB9CgphYngycGxvdC5mY2x1c3QgJT4lCiAgICBmaWx0ZXIoRmVhdHVyZV9JRCAlaW4lIHNlcV90b19wbG90KSAlPiUKZ2dwbG90KGFlcyh4ID0gdGltZSwgeSA9IHZhbHVlLCBjb2xvciA9IFN1YmplY3RfQ2x1c3RlcikpICsKICBnZW9tX2xpbmUoCiAgICBhZXMoZ3JvdXAgPSBSZXBsaWNhdGVfSUQpLAogICAgYWxwaGEgPSAwLjMsIHNpemUgPSAwLjUKICApICsKICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKSArCiAgZ2VvbV9wb2ludCgKICAgIGRhdGEgPSBhYngycGxvdCwKICAgIGFlcyh4ID0gQWJ4X1JlbERheSwgeSA9IGFidW5kYW5jZSksCiAgICBzaXplID0gMC4zLCBhbHBoYSA9IDAuNQogICkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIGx3ZCA9IDEsIGNvbG9yID0gIm9yYW5nZSIpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSA0LCBsd2QgPSAxLCBjb2xvciA9ICJvcmFuZ2UiKSArCiAgZ2VvbV9saW5lKAogICAgZGF0YSA9IGFieF9mY2x1c3RfbXUgJT4lIAogICAgICBmaWx0ZXIoRmVhdHVyZV9JRCAlaW4lIHNlcV90b19wbG90KSAlPiUgCiAgICAgIGxlZnRfam9pbihUQVhUQUIsIGJ5ID0gYygiRmVhdHVyZV9JRCIgPSAiU2VxX0lEIikpICU+JQogICAgICBtdXRhdGUoT3JnTmFtZSA9IGZhY3RvcihPcmdOYW1lLCBsZXZlbHMgPSB0YXhfdG9fcGxvdCkpLAogICAgY29sb3IgPSAicmVkIiwgc2l6ZSA9IDEKICApICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyggImdyZXkxNyIsICJkZWVwc2t5Ymx1ZTIiKSkgKwogIGZhY2V0X3dyYXAofiBPcmdOYW1lLCBzY2FsZXMgPSAiZnJlZSIsIG5jb2wgPSA0KSArCiAgdGhlbWUoc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHk9IkhlbHZldGljYS1OYXJyb3ciLCBzaXplID0gMTApKSArCiAgc2NhbGVfeF9jb250aW51b3VzKG5hbWUgPSAiRGF5cyBmcm9tIGluaXRpYWwgYW50aWJpb3RpYyBkb3NlIiwKICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gc2VxKC00MCwgMTIwLCAyMCksIGxhYmVscyA9ICBzZXEoLTQwLCAxMjAsIDIwKSwKICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYyhOQSwgTkEpKSAKYGBgCgoKIyMjIE1pY3JvYmUgY2x1c3RlcnMKClVzaW5nIG1lYW4gcmVzcG9uc2UgdG8gQWJ4CgpgYGB7cn0KYWJ4X2ZjbHVzdF9tYWpvcml0eSA8LSBhYngycGxvdC5mY2x1c3QgJT4lCiAgZmlsdGVyKFN1YmplY3RfQ2x1c3RlciA9PSAiTWFqb3JpdHkiKSAlPiUKICBsZWZ0X2pvaW4oa2VlcF9yc3YsIGJ5ID0gYygiRmVhdHVyZV9JRCIgPSAiU2VxX0lEIikpICU+JQogIGZpbHRlcihGcmVxID49IG1pbl9udW1fc3ViaikgJT4lCiAgc2VsZWN0KC1GcmVxKSAlPiUKICBncm91cF9ieSh0aW1lLCBGZWF0dXJlX0lEKSAlPiUKICBzdW1tYXJpc2UodmFsdWUgPSBtZWFuKHZhbHVlKSkgJT4lCiAgbGVmdF9qb2luKFRBWFRBQiwgYnkgPSBjKCJGZWF0dXJlX0lEIiA9ICJTZXFfSUQiKSkgJT4lCiAgYXJyYW5nZSggRmVhdHVyZV9JRCwgdGltZSkKCmxlbmd0aCh1bmlxdWUoYWJ4X2ZjbHVzdF9tYWpvcml0eSRGZWF0dXJlX0lEKSkKI1sxXSA1NzMKCnNldC5zZWVkKDEyMzQ1NikKbWluX251bV9zdWJqIDwtIDUKc2VxQ2x1c3RlcnMuYWJ4IDwtIGZpdF9mcGNhKAogIGFieF9mY2x1c3RfbWFqb3JpdHksCiAgInRpbWUiLCAidmFsdWUiLCAiRmVhdHVyZV9JRCIsCiAgY2x1c3RlciA9IFRSVUUsIEsgPSA4KQoKc2VxQ2x1c3RlcnNfZnBjYS5hYnggPC0gZml0dGVkX3ZhbHVlc19mcGNhKHNlcUNsdXN0ZXJzLmFieCkgJT4lCiAgbXV0YXRlKFNlcV9JRCA9IFJlcGxpY2F0ZV9JRCkgCmBgYAoKYGBge3J9CiMgd3JpdGUuY3N2KHNlcUNsdXN0ZXJzX2ZwY2EuYWJ4ICU+JSBzZWxlY3QoU2VxX0lELCBDbHVzdGVyKSAlPiUgZGlzdGluY3QoKSAlPiUgCiMgICAgICAgICAgICAgbGVmdF9qb2luKFRBWFRBQikgJT4lIGFycmFuZ2UoQ2x1c3RlciksCiMgICAgICAgICAgIGZpbGUgPSAib3V0cHV0L2FieF9zZXFDbHVzdGVycy5jc3YiKQpgYGAKCgoKCmBgYHtyIHNlcS1jbHVzdGVycy1hYngsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD02fQpnZ3Bsb3QoCiAgc2VxQ2x1c3RlcnNfZnBjYS5hYngsIGFlcyh4ID0gdGltZSwgeSA9IHZhbHVlKSkgKwogIGdlb21fbGluZSgKICAgIGFlcyhncm91cCA9IFNlcV9JRCwgY29sb3IgPSBDbHVzdGVyKSwKICAgIHNpemUgPSAwLjcsIGFscGhhID0gMC43CiAgKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgbHdkID0gMSwgY29sb3IgPSAib3JhbmdlIikgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDUsIGx3ZCA9IDEsIGNvbG9yID0gIm9yYW5nZSIpICsKICBnZW9tX3Ntb290aChjb2xvciA9ICJncmV5MjAiKSArCiAgZmFjZXRfd3JhcCh+IENsdXN0ZXIsIGxhYmVsbGVyID0gbGFiZWxfYm90aCwgbmNvbCA9IDQpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKwogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9NCkpKQpgYGAKCgpgYGB7cn0KdG9wX3JzdiA8LSBzZXFDbHVzdGVyc19mcGNhLmFieCAlPiUKICBncm91cF9ieShTZXFfSUQsIENsdXN0ZXIpICU+JQogIHN1bW1hcmlzZShtZWFuX2FibmQgPSBtZWFuKHZhbHVlKSkgJT4lCiAgYXJyYW5nZShkZXNjKG1lYW5fYWJuZCkpICU+JQogIGxlZnRfam9pbihUQVhUQUIgJT4lIHNlbGVjdChTZXFfSUQsIE9yZ05hbWUsIFNwZWNpZXMsIEdlbnVzKSkKCnRvcF9nZW51cyA8LSB0b3BfcnN2ICU+JQogIGZpbHRlcighaXMubmEoR2VudXMpKSAlPiUKICBncm91cF9ieShHZW51cywgQ2x1c3RlcikgJT4lCiAgc3VtbWFyaXNlKAogICAgbWVhbl9hYm5kID0gbWVhbihtZWFuX2FibmQpLAogICAgcHJldiA9IG4oKSkgJT4lCiAgYXJyYW5nZShDbHVzdGVyLCAtcHJldiwgLW1lYW5fYWJuZCkgCmBgYAoKYGBge3J9Cm4gPSA2OyBuY2x1c3QgPSBsZW5ndGgodW5pcXVlKHRvcF9nZW51cyRDbHVzdGVyKSkKZGZfdG9wX2dlbnVzIDwtIHRvcF9nZW51cyAlPiUgCiAgZ3JvdXBfYnkoQ2x1c3RlcikgJT4lIAogIHRvcF9uKG4sIHd0ID0gcHJldiptZWFuX2FibmQpICU+JQogIGFycmFuZ2UoQ2x1c3RlciwgLXByZXYsIC1tZWFuX2FibmQpICU+JQogIHNlbGVjdCgtbWVhbl9hYm5kLCAtcHJldikgJT4lIHVuZ3JvdXAoKSAlPiUKICBtdXRhdGUoQ2x1c3RlciA9IHBhc3RlMCgiQ2x1c3RlciIsIENsdXN0ZXIpKSAlPiUKICBtdXRhdGUoaWR4ID0gcmVwKDE6biwgbmNsdXN0KSkgJT4lCiAgc3ByZWFkKGtleSA9IENsdXN0ZXIsIEdlbnVzKQpkZl90b3BfZ2VudXMKYGBgCgoKYGBge3J9Cm4gPSAxMDsgbmNsdXN0ID0gbGVuZ3RoKHVuaXF1ZSh0b3BfcnN2JENsdXN0ZXIpKQpkZl90b3BfcnN2IDwtIHRvcF9yc3YgJT4lIAogIHVuZ3JvdXAoKSAlPiUKICBzZWxlY3QoT3JnTmFtZSwgQ2x1c3RlciwgbWVhbl9hYm5kKSAlPiUKICBncm91cF9ieShDbHVzdGVyKSAlPiUgCiAgdG9wX24obiwgd3QgPSBtZWFuX2FibmQpICU+JQogIGFycmFuZ2UoQ2x1c3RlciwgbWVhbl9hYm5kKSAlPiUKICBzZWxlY3QoLW1lYW5fYWJuZCkgJT4lIHVuZ3JvdXAoKSAlPiUKICBtdXRhdGUoQ2x1c3RlciA9IHBhc3RlMCgiQ2x1c3RlciIsIENsdXN0ZXIpKSAlPiUKICBtdXRhdGUoaWR4ID0gcmVwKDE6biwgbmNsdXN0KSkgJT4lCiAgc3ByZWFkKGtleSA9IENsdXN0ZXIsIE9yZ05hbWUpCmRmX3RvcF9yc3YgJT4lIHNlbGVjdCgtaWR4KQpgYGAKCgoKCiMjIERpZXQKCgpgYGB7cn0KYnJheV90b19iYXNlbGluZV9mbHRyICU+JSAKICBmaWx0ZXIocGVydHVyYmF0aW9uID09ICJEaWV0IiwgUmVsRGF5ID49IC0zMCwgUmVsRGF5IDw9IDMwKSAlPiUKICBtdXRhdGUoSW50ZXJ2YWwgPSBmYWN0b3IoSW50ZXJ2YWwsIGxldmVsID0gbmFtZXMoZGlldF9pbnR2X2NvbHMpKSkgJT4lCiAgZ2dwbG90KAogICAgYWVzKHggPSBSZWxEYXksIHkgPSBkaXN0X3RvX2Jhc2VsaW5lLCAKICAgICAgICBncm91cCA9IFN1YmplY3QsIGNvbG9yID0gSW50ZXJ2YWwpKSArCiAgZ2VvbV9saW5lKGFlcyhncm91cCA9IFN1YmplY3QpLCBhbHBoYSA9IDAuNywgbHdkID0gMC41KSArIAogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUsIHNpemUgPSAxLjIpICsgCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGRpZXRfaW50dl9jb2xzKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSArIAogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTMpKSkgKwogIHhsYWIoIkRheXMgZnJvbSBkaWV0IGluaXRpYXRpb24iKSArCiAgeWxhYigiQnJheS1DdXJ0aXMgZGlzdGFuY2UgdG8gNyBwcmUtZGlldCBzYW1wbGVzIikgCmBgYAoKCmBgYHtyLCBldmFsID0gRkFMU0V9CmZwY2EuYnJheS5kaWV0MzAgPC0gZml0X2Rpc3RfdG9fYmFzZWxpbmUoCiAgYnJheV90b19iYXNlbGluZV9mbHRyICU+JSBmaWx0ZXIocGVydHVyYmF0aW9uID09ICJEaWV0IiwgUmVsRGF5ID49IC0zMCwgUmVsRGF5IDw9IDMwKSkKCnNhdmUobGlzdCA9IGMoImZwY2EuYnJheS5hYngiLCAiZnBjYS5icmF5LmRpZXQiLCAiZnBjYS5icmF5LmRpZXQzMCIpLCBmaWxlID0gIm91dHB1dC9mcGNhX3Jlcy5yZGEiKQpgYGAKCmBgYHtyIGZwY2EtYnJheS1kaWV0LCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9Ni41fQoocERpZXQgPC0gYnJheV90b19iYXNlbGluZV9mbHRyICU+JSAKICBmaWx0ZXIocGVydHVyYmF0aW9uID09ICJEaWV0IiwgUmVsRGF5ID49IC0zMCwgUmVsRGF5IDw9IDMwKSAlPiUKZ2dwbG90KGFlcyh4ID0gUmVsRGF5LCB5ID0gZGlzdF90b19iYXNlbGluZSkpICsKICBnZW9tX2xpbmUoCiAgICBkYXRhID0gZnBjYS5icmF5LmRpZXQzMFtbImZpdHRlZCJdXSwKICAgIGFlcyhncm91cCA9IFN1YmplY3QsIHggPSB0aW1lLCB5ID0gdmFsdWUpLAogICAgYWxwaGEgPSAwLjMsIHNpemUgPSAwLjcsIGNvbG9yID0gImdyZXkzMCIpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBsd2QgPSAxLCBjb2xvciA9ICJvcmFuZ2UiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gNCwgbHdkID0gMSwgY29sb3IgPSAib3JhbmdlIikgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gSW50ZXJ2YWwpLCBzaXplID0gMS41LCBhbHBoYSA9IDAuNykgKwogIGdlb21fbGluZSgKICAgIGRhdGEgPSBmcGNhLmJyYXkuZGlldDMwW1sibWVhbiJdXSwgYWVzKHggPSB0aW1lLCB5ID0gdmFsdWUpLAogICAgY29sb3IgPSAibmF2eSIsIHNpemUgPSAyKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGRpZXRfaW50dl9jb2xzLCBuYW1lID0gIkludGVydmFsIikgKwogIHNjYWxlX3hfY29udGludW91cygKICAgIG5hbWUgPSAiRGF5cyBmcm9tIGRpZXQgaW5pdGlhdGlvbiIsCiAgICBsaW1pdHMgPSBjKE5BLCBOQSksIGJyZWFrcyA9IHNlcSgtNTAsIDYwLCAxMCkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoCiAgICBuYW1lID0gIkJyYXktQ3VydGlzIGRpc3RhbmNlIHRvIGJhc2VsaW5lIiwgCiAgICBsaW1pdHMgPSBjKE5BLCBOQSksIGJyZWFrcyA9IHNlcSgwLjEsIDAuODAsIDAuMSkpICsKICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAyMCkpKSAKYGBgCgoKYGBge3IgZnBjYS1kZXJpdi1icmF5LWRpZXQsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD02LjV9CmZwY2EuYnJheS5kaWV0MzBbWyJmaXR0ZWQiXV0gPC0gZnBjYS5icmF5LmRpZXQzMFtbImZpdHRlZCJdXSAlPiUKICBtdXRhdGUoCiAgICBJbnRlcnZhbCA9IGlmZWxzZShmcGNhLmJyYXkuZGlldDMwW1siZml0dGVkIl1dJHRpbWUgPCAwICwgIlByZURpZXQiLAogICAgICAgICAgICAgICBpZmVsc2UoZnBjYS5icmF5LmRpZXQzMFtbImZpdHRlZCJdXSR0aW1lID49IDAgJiBmcGNhLmJyYXkuZGlldDMwW1siZml0dGVkIl1dJHRpbWUgPD0gNCwgIk1pZERpZXQiLAogICAgICAgICAgICAgICAgICAgICAgIlBvc3REaWV0IikpKQoKKHBEaWV0RGVyaXYgPC0gIGZwY2EuYnJheS5kaWV0MzBbWyJmaXR0ZWQiXV0gJT4lCiAgZ2dwbG90KGFlcyh4ID0gUmVsRGF5KSkgKwogIGdlb21fbGluZSgKICAgIGFlcyhncm91cCA9IFN1YmplY3QsIHggPSB0aW1lLCB5ID0gZGVyaXYsIGNvbG9yID0gSW50ZXJ2YWwpLAogICAgYWxwaGEgPSAwLjUsIHNpemUgPSAwLjcpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBsd2QgPSAxLCBjb2xvciA9ICJvcmFuZ2UiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gNCwgbHdkID0gMSwgY29sb3IgPSAib3JhbmdlIikgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBkaWV0X2ludHZfY29scywgbmFtZSA9ICJJbnRlcnZhbCIpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobmFtZSA9ICIiLAogICAgbGltaXRzID0gYyhOQSwgTkEpLCBicmVha3MgPSBzZXEoLTUwLCA2MCwgMTApKSArCiAgeWxhYigiRGVyaXZhdGl2ZSIpKQpgYGAKCgpgYGB7ciBmcGNhLWJyYXktZGlldC1sYWJzLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9Ni41fQoocERpZXRMYWIgPC0gYnJheV90b19iYXNlbGluZV9mbHRyICU+JSAKICBmaWx0ZXIocGVydHVyYmF0aW9uID09ICJEaWV0IiwgUmVsRGF5ID49IC01MCwgUmVsRGF5IDw9IDYwKSAlPiUKZ2dwbG90KGFlcyh4ID0gUmVsRGF5LCB5ID0gZGlzdF90b19iYXNlbGluZSkpICsKICBnZW9tX2xpbmUoCiAgICBkYXRhID0gZnBjYS5icmF5LmRpZXRbWyJmaXR0ZWQiXV0sCiAgICBhZXMoZ3JvdXAgPSBTdWJqZWN0LCB4ID0gdGltZSwgeSA9IHZhbHVlKSwKICAgIGFscGhhID0gMC4zLCBzaXplID0gMC43ICkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIGx3ZCA9IDEsIGNvbG9yID0gIm9yYW5nZSIpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSA0LCBsd2QgPSAxLCBjb2xvciA9ICJvcmFuZ2UiKSArCiAgZ2VvbV90ZXh0KAogICAgICBhZXMobGFiZWwgPSBTdWJqZWN0LCBjb2xvciA9IEludGVydmFsKSwgc2l6ZSA9IDQsIGFscGhhID0gMC43KSArCiAgZ2VvbV9saW5lKAogICAgZGF0YSA9IGZwY2EuYnJheS5kaWV0W1sibWVhbiJdXSwgYWVzKHggPSB0aW1lLCB5ID0gdmFsdWUpLAogICAgY29sb3IgPSAibmF2eSIsIHNpemUgPSAyKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGRpZXRfaW50dl9jb2xzLCBuYW1lID0gIkludGVydmFsIikgKwogIHNjYWxlX3hfY29udGludW91cygKICAgIG5hbWUgPSAiRGF5cyBmcm9tIGRpZXQgaW5pdGlhdGlvbiIsCiAgICBsaW1pdHMgPSBjKE5BLCBOQSksIGJyZWFrcyA9IHNlcSgtNTAsIDYwLCAxMCkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoCiAgICBuYW1lID0gIkJyYXktQ3VydGlzIGRpc3RhbmNlIHRvIGJhc2VsaW5lIiwgCiAgICBsaW1pdHMgPSBjKDAuMSwgMC44NSksIGJyZWFrcyA9IHNlcSgwLjEsIDAuODAsIDAuMSkpICsKICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAyMCkpKQoKYGBgCgoKYGBge3IgZnBjYS1icmF5LWRpZXQtdmFsLWRlcml2LCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9Ni41fQp2cCA9IGdyaWQ6OnZpZXdwb3J0KHdpZHRoID0gMC4zLCBoZWlnaHQgPSAwLjMyLCB4ID0gMC4wNywgeSA9MC45NSwganVzdCA9IGMoImxlZnQiLCAidG9wIikpCnByaW50KHBEaWV0KQpwcmludChwRGlldERlcml2ICsgdGhlbWVfYncoYmFzZV9zaXplID0gMTApICsgdGhlbWVfc3VicGxvdCwgdnAgPSB2cCkKYGBgCgoKCiMjIyBDbHVzdGVyIFJTVnMKCmBgYHtyLCBldmFsID0gRkFMU0V9CmRpZXRGYWMgPC0gYygiUHJlRGlldCIsICJNaWREaWV0IiwgIlBvc3REaWV0IikKZGlldCA8LSBzdWJzZXRfc2FtcGxlcyhwc1N1YmosIERpZXRfUmVsRGF5ID49IC0zMCAmIERpZXRfUmVsRGF5IDw9IDMwKQpkaWV0IDwtIHN1YnNldF90YXhhKGRpZXQsIHRheGFfc3VtcyhkaWV0KSA+IDApCmRpZXQKCihyZXBsaWNhdGVkIDwtIGRhdGEuZnJhbWUoc2FtcGxlX2RhdGEoZGlldCkpICU+JSAKICAgICAgICBncm91cF9ieShTdWJqZWN0LCBHcm91cCwgRGlldF9JbnRlcnZhbCwgRGlldF9SZWxEYXkpICU+JQogICAgICAgIG11dGF0ZShuID0gbigpKSAlPiUgdW5ncm91cCgpICU+JSAKICAgICAgICBmaWx0ZXIobiA+IDEpICU+JSBzZWxlY3QoTWVhc19JRCwgU3ViamVjdCwgRGlldF9SZWxEYXksIERpZXRfSW50ZXJ2YWwpKQoKIyBSZW1vdmUgcmVwbGljYXRlZCBzYW1wbGVzCnNhbXBsZV9zdW1zKGRpZXQpW3JlcGxpY2F0ZWQkTWVhc19JRF0KIyBNNzg2OSBNNzg4NiAKIyA3NDY5NyA3NDg3MyAKCiMgd2UgcmV0YWluIGEgcmVwbGljYXRlIHdpdGggaGlnaGVyIHNhbXBsZSBkZXB0aDoKZGlldCA8LSBzdWJzZXRfc2FtcGxlcyhkaWV0LCAhTWVhc19JRCAlaW4lIGMoIk0yMTI5IikpCmRpZXQKIyBvdHVfdGFibGUoKSAgIE9UVSBUYWJsZTogICAgICAgICBbIDIzMzMgdGF4YSBhbmQgMjYxMSBzYW1wbGVzIF0KYGBgCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpkaWV0LnJzdiA8LSBkYXRhLmZyYW1lKGFzaW5oKGFzKG90dV90YWJsZShkaWV0KSwgIm1hdHJpeCIpKSkgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCJTZXFfSUQiKSAlPiUKICBnYXRoZXIoTWVhc19JRCwgYWJ1bmRhbmNlLCAtU2VxX0lEKSAlPiUKICBsZWZ0X2pvaW4oU01QKSAlPiUKICBsZWZ0X2pvaW4oVEFYVEFCKSAlPiUKICBtdXRhdGUoRGlldF9JbnRlcnZhbCA9IGZhY3RvcihEaWV0X0ludGVydmFsLCBsZXZlbHMgPSBkaWV0RmFjKSkgJT4lCiAgYXJyYW5nZShTZXFfSUQsIFN1YmplY3QsIERpZXRfUmVsRGF5KQoKdGhyZXNoIDwtIDA7IG51bV9ub256ZXJvIDwtIDEwOyAKbWluX25vX3N1YmogPC0gMwoKa2VlcF9yc3YgPC0gZGlldC5yc3YgJT4lCiAgZmlsdGVyKGFidW5kYW5jZSA+IHRocmVzaCkgJT4lCiAgZ3JvdXBfYnkoU3ViamVjdCwgU2VxX0lEKSAlPiUKICBzdW1tYXJpc2UoRnJlcSA9IG4oKSkgJT4lICAgIyBOby4gb2Ygc2FtcGxlcyBwZXIgc3ViamVjdCB3LyBwb3NpdGl2ZSByc3YgY291bnQgCiAgZmlsdGVyKEZyZXEgPj0gbnVtX25vbnplcm8pICU+JQogIGdyb3VwX2J5KFNlcV9JRCkgJT4lCiAgc3VtbWFyaXNlKEZyZXEgPSBuKCkpICU+JSAjIE51bWJlciBvZiBzdWJqZWN0cyB3LyBudW1fbm9uemVybyBzYW1wbGVzIHdpdGggY291bnRzID4gdGhyZXNoCiAgZmlsdGVyKEZyZXEgPj0gbWluX25vX3N1YmopICU+JQogIGFycmFuZ2UoZGVzYyhGcmVxKSkKCmxlbmd0aCh1bmlxdWUoa2VlcF9yc3YkU2VxX0lEKSkgIyA9IDc1NAoKZGlldC5yc3Yuc3Vic2V0IDwtIGRpZXQucnN2ICU+JQogIGZpbHRlcihTZXFfSUQgJWluJSB1bmlxdWUoa2VlcF9yc3YkU2VxX0lEKSkKCiMgc2F2ZShsaXN0ID0gYygia2VlcF9yc3YiLCAiZGlldC5yc3Yuc3Vic2V0IiksIAojICAgICAgZmlsZSA9ICJvdXRwdXQvZnBjYV9jbHVzdF9yZXNfZGlldC5yZGEiKQpgYGAKCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpkaWV0X2ZjbHVzdCA8LSBmcGNhX3dyYXBwZXIoCiAgZGlldC5yc3Yuc3Vic2V0LAogIHRpbWVfY29sdW1uID0gIkRpZXRfUmVsRGF5IiwKICB2YWx1ZV9jb2x1bW4gPSAiYWJ1bmRhbmNlIiwKICByZXBsaWNhdGVfY29sdW1uID0gIlN1YmplY3QiLAogIGZlYXRfY29sdW1uID0gIlNlcV9JRCIsCiAgY2x1c3RlciA9IFRSVUUsCiAgY2x1c3RfbWluX251bV9yZXBsaWNhdGUgPSAxNSwKICBmcGNhX29wdG5zID0gTlVMTCwgZmNsdXN0X29wdG5zID0gTlVMTCwKICBwYXJhbGxlbCA9IFRSVUUsIG5jb3JlcyA9IDE2KQoKZGlldF9mY2x1c3RfbXUgPC0gZ2V0X2ZwY2FfbWVhbnMoZGlldF9mY2x1c3QpCmRpZXRfZmNsdXN0X3N1YmpGaXQgPC0gZ2V0X2ZwY2FfZml0cyhkaWV0X2ZjbHVzdCkKCnNhdmUobGlzdCA9IGMoImtlZXBfcnN2IiwgImRpZXQucnN2LnN1YnNldCIsIAogICAgICAgICAgICAgICJkaWV0X2ZjbHVzdCIsICJkaWV0X2ZjbHVzdF9zdWJqRml0IiwgImRpZXRfZmNsdXN0X211IiksCiAgICBmaWxlID0gIm91dHB1dC9mcGNhX2NsdXN0X3Jlc19kaWV0LnJkYSIpCmBgYAoKV2Ugbm93IGZpbmQgdGhlIHRheGEgd2l0aCB0aGUgbW9zdCB2YXJpYWJpbGl0eSBpbiB0aGUgcmVzcG9uc2UgdG8gRGlldCBiZXR3ZWVuIHR3byBjbHVzdGVyczoKCmBgYHtyfQojIFRheGEncyB0cmFqZWN0b3J5IGRpZmZlcmVuY2UgYmV0d2VlbiB0d28gY2x1c3RlcnMKbG9hZCgib3V0cHV0L2ZwY2FfY2x1c3RfcmVzX2RpZXQucmRhIikKZGlmZkJldHdlZW5DbHVzdHMuZGlldCA8LSBkaWV0X2ZjbHVzdF9zdWJqRml0ICU+JQogIGdyb3VwX2J5KEZlYXR1cmVfSUQsIHRpbWUsIENsdXN0ZXIgKSAlPiUgCiAgc3VtbWFyaXNlKHZhbHVlID0gbWVhbih2YWx1ZSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgc3ByZWFkKGtleSA9ICJDbHVzdGVyIiwgdmFsdWUgPSAidmFsdWUiKSAlPiUKICB1bmdyb3VwKCkgJT4lIAogIG11dGF0ZShkaWZmID0gYWJzKGAxYCAtIGAyYCkpICU+JQogIGdyb3VwX2J5KEZlYXR1cmVfSUQpICU+JQogIHN1bW1hcmlzZSgKICAgIG1lYW5fY2x1c3REaXN0X0wxID0gbWVhbihkaWZmLCBuYS5ybSA9IFRSVUUpLAogICAgc2RfQ2x1c3QxID0gc2QoYDFgKSwKICAgIHNkX0NsdXN0MiA9IHNkKGAyYCkpICU+JSAKICBmaWx0ZXIoCiAgICAhaXMubmEobWVhbl9jbHVzdERpc3RfTDEpCiAgKSAlPiUgIyBTb21lIHRheGEgaGF2ZSBhIHNpbmdsZSBjbHVzdGVyIChldmVyeW9uZSBiZWhhdmVzIHNpbWlsYXJseSkKICBhcnJhbmdlKC1tZWFuX2NsdXN0RGlzdF9MMSkKCnN1bW1hcnkoZGlmZkJldHdlZW5DbHVzdHMuZGlldCkKCmRpZmZCZXR3ZWVuQ2x1c3RzLmRpZXQgPC0gZGlmZkJldHdlZW5DbHVzdHMuZGlldCAlPiUKICBmaWx0ZXIoc2RfQ2x1c3QxID4gbWVkaWFuKGRpZmZCZXR3ZWVuQ2x1c3RzLmRpZXQkc2RfQ2x1c3QxKSB8CiAgICAgICAgICAgc2RfQ2x1c3QyID4gbWVkaWFuKGRpZmZCZXR3ZWVuQ2x1c3RzLmRpZXQkc2RfQ2x1c3QyKSkKYGBgCgoKYGBge3J9CiMgUGxvdCB0b3AgMjAKc2VxX3RvX3Bsb3QgPC0gZGlmZkJldHdlZW5DbHVzdHMuZGlldCRGZWF0dXJlX0lEWzE6MjBdCnRheF90b19wbG90IDwtIChUQVhUQUIgJT4lIGNvbHVtbl90b19yb3duYW1lcygiU2VxX0lEIikpW3NlcV90b19wbG90LCAiT3JnTmFtZSJdCnRheF90b19wbG90CgoKc2VxMV9zdWJqQ2x1c3RlcnMgPC0gZGlldF9mY2x1c3Rfc3ViakZpdCAlPiUKICBmaWx0ZXIoRmVhdHVyZV9JRCA9PSAiU2VxMSIpICU+JQogIHNlbGVjdChSZXBsaWNhdGVfSUQsIENsdXN0ZXIpICU+JSBkaXN0aW5jdCgpICU+JQogIGZpbHRlcihDbHVzdGVyPT0yKQoKZGlldDJwbG90LmZjbHVzdCA8LSBkaWV0X2ZjbHVzdF9zdWJqRml0ICU+JQogIGxlZnRfam9pbihUQVhUQUIsIGJ5ID0gYygiRmVhdHVyZV9JRCIgPSAiU2VxX0lEIikpICU+JQogIGdyb3VwX2J5KEZlYXR1cmVfSUQpICU+JQogIG11dGF0ZSgKICAgIG5fY2x1c3RlcjEgPSBzdW0oQ2x1c3RlciA9PSAxKSwKICAgIG5fY2x1c3RlcjIgPSBzdW0oQ2x1c3RlciA9PSAyKSwKICAgIG1ham9yaXR5Q2x1c3RlciA9IGlmZWxzZShuX2NsdXN0ZXIxID4gbl9jbHVzdGVyMiwgMSwgMikKICApICU+JSAKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKAogICAgT3JnTmFtZSA9IGZhY3RvcihPcmdOYW1lLCBsZXZlbHMgPSB0YXhfdG9fcGxvdCksCiAgICBTdWJqZWN0X0NsdXN0ZXIgPSBpZmVsc2UoQ2x1c3RlciA9PSBtYWpvcml0eUNsdXN0ZXIsICJNYWpvcml0eSIsICJNaW5vcml0eSIpKSAKCgpkaWV0MnBsb3QgPC0gZGlldC5yc3Yuc3Vic2V0ICU+JSAKICBmaWx0ZXIoU2VxX0lEICVpbiUgc2VxX3RvX3Bsb3QpICU+JQogIGxlZnRfam9pbigKICAgIGRpZXQycGxvdC5mY2x1c3QgJT4lIAogICAgICBzZWxlY3QoLXZhbHVlLCAtdGltZSkgJT4lCiAgICAgIGRpc3RpbmN0KCkgJT4lCiAgICAgIHJlbmFtZShTZXFfSUQgPSBGZWF0dXJlX0lELCAgU3ViamVjdCA9IFJlcGxpY2F0ZV9JRCkgCiAgKSAlPiUKICBtdXRhdGUoCiAgICBTZXFfSUQgPSBmYWN0b3IoU2VxX0lELCBsZXZlbHMgPSBzZXFfdG9fcGxvdCksCiAgICBPcmdOYW1lID0gZmFjdG9yKE9yZ05hbWUsIGxldmVscyA9IHRheF90b19wbG90KSkKYGBgCgoKCmBgYHtyIGRpZmZUYXhhLWRpZXQsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD0xMH0KCmRpZXQycGxvdC5mY2x1c3QgJT4lCiAgICBmaWx0ZXIoRmVhdHVyZV9JRCAlaW4lIHNlcV90b19wbG90KSAlPiUKZ2dwbG90KGFlcyh4ID0gdGltZSwgeSA9IHZhbHVlLCBjb2xvciA9IFN1YmplY3RfQ2x1c3RlcikpICsKICBnZW9tX2xpbmUoCiAgICBhZXMoZ3JvdXAgPSBSZXBsaWNhdGVfSUQpLAogICAgYWxwaGEgPSAwLjMsIHNpemUgPSAwLjUKICApICsKICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKSArCiAgZ2VvbV9wb2ludCgKICAgIGRhdGEgPSBkaWV0MnBsb3QsCiAgICBhZXMoeCA9IERpZXRfUmVsRGF5LCB5ID0gYWJ1bmRhbmNlKSwKICAgIHNpemUgPSAwLjMsIGFscGhhID0gMC41CiAgKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgbHdkID0gMSwgY29sb3IgPSAib3JhbmdlIikgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDQsIGx3ZCA9IDEsIGNvbG9yID0gIm9yYW5nZSIpICsKICBnZW9tX2xpbmUoCiAgICBkYXRhID0gZGlldF9mY2x1c3RfbXUgJT4lIAogICAgICBmaWx0ZXIoRmVhdHVyZV9JRCAlaW4lIHNlcV90b19wbG90KSAlPiUgCiAgICAgIGxlZnRfam9pbihUQVhUQUIsIGJ5ID0gYygiRmVhdHVyZV9JRCIgPSAiU2VxX0lEIikpICU+JQogICAgICBtdXRhdGUoT3JnTmFtZSA9IGZhY3RvcihPcmdOYW1lLCBsZXZlbHMgPSB0YXhfdG9fcGxvdCkpLAogICAgY29sb3IgPSAicmVkIiwgc2l6ZSA9IDEKICApICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyggImdyZXkxNyIsICJkZWVwc2t5Ymx1ZTIiKSkgKwogIGZhY2V0X3dyYXAofiBPcmdOYW1lLCBzY2FsZXMgPSAiZnJlZSIsIG5jb2wgPSA0KSArCiAgdGhlbWUoc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHk9IkhlbHZldGljYS1OYXJyb3ciLCBzaXplID0gMTApKSArCiAgc2NhbGVfeF9jb250aW51b3VzKG5hbWUgPSAiRGF5cyBmcm9tIGRpZXQgaW5pdGlhdGlvbiIsCiAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IHNlcSgtNDAsIDEyMCwgMjApLCBsYWJlbHMgPSAgc2VxKC00MCwgMTIwLCAyMCksCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoTkEsIE5BKSkgCmBgYAoKCiMjIyBNaWNyb2JlIGNsdXN0ZXJzCgpVc2luZyBtZWFuIHJlc3BvbnNlIHRvIERpZXQKCmBgYHtyfQpkaWV0X2ZjbHVzdF9tYWpvcml0eSA8LSBkaWV0MnBsb3QuZmNsdXN0ICU+JQogIGZpbHRlcihTdWJqZWN0X0NsdXN0ZXIgPT0gIk1ham9yaXR5IikgJT4lCiAgbGVmdF9qb2luKGtlZXBfcnN2LCBieSA9IGMoIkZlYXR1cmVfSUQiID0gIlNlcV9JRCIpKSAlPiUKICBmaWx0ZXIoRnJlcSA+PSBtaW5fbnVtX3N1YmopICU+JQogIHNlbGVjdCgtRnJlcSkgJT4lCiAgZ3JvdXBfYnkodGltZSwgRmVhdHVyZV9JRCkgJT4lCiAgc3VtbWFyaXNlKHZhbHVlID0gbWVhbih2YWx1ZSkpICU+JQogIGxlZnRfam9pbihUQVhUQUIsIGJ5ID0gYygiRmVhdHVyZV9JRCIgPSAiU2VxX0lEIikpICU+JQogIGFycmFuZ2UoIEZlYXR1cmVfSUQsIHRpbWUpCgpsZW5ndGgodW5pcXVlKGRpZXRfZmNsdXN0X21ham9yaXR5JEZlYXR1cmVfSUQpKQojWzFdIDUwMAoKc2V0LnNlZWQoMTIzNDU2KQptaW5fbnVtX3N1YmogPC0gNQpzZXFDbHVzdGVycy5kaWV0IDwtIGZpdF9mcGNhKAogIGRpZXRfZmNsdXN0X21ham9yaXR5LAogICJ0aW1lIiwgInZhbHVlIiwgIkZlYXR1cmVfSUQiLAogIGNsdXN0ZXIgPSBUUlVFLCBLID0gNikKCnNlcUNsdXN0ZXJzX2ZwY2EuZGlldCA8LSBmaXR0ZWRfdmFsdWVzX2ZwY2Eoc2VxQ2x1c3RlcnMuZGlldCkgJT4lCiAgbXV0YXRlKFNlcV9JRCA9IFJlcGxpY2F0ZV9JRCkgCmBgYAoKYGBge3J9CndyaXRlLmNzdihzZXFDbHVzdGVyc19mcGNhLmRpZXQgJT4lIHNlbGVjdChTZXFfSUQsIENsdXN0ZXIpICU+JSBkaXN0aW5jdCgpICU+JQogICAgICAgICAgICBsZWZ0X2pvaW4oVEFYVEFCKSAlPiUgYXJyYW5nZShDbHVzdGVyKSwKICAgICAgICAgIGZpbGUgPSAib3V0cHV0L2RpZXRfc2VxQ2x1c3RlcnMuY3N2IikKYGBgCgoKCgpgYGB7ciBzZXEtY2x1c3RlcnMtZGlldCwgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9NH0KZ2dwbG90KAogIHNlcUNsdXN0ZXJzX2ZwY2EuZGlldCwgYWVzKHggPSB0aW1lLCB5ID0gdmFsdWUpKSArCiAgZ2VvbV9saW5lKAogICAgYWVzKGdyb3VwID0gU2VxX0lELCBjb2xvciA9IENsdXN0ZXIpLAogICAgc2l6ZSA9IDAuNywgYWxwaGEgPSAwLjUKICApICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBsd2QgPSAxLCBjb2xvciA9ICJvcmFuZ2UiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gNSwgbHdkID0gMSwgY29sb3IgPSAib3JhbmdlIikgKwogIGdlb21fc21vb3RoKGNvbG9yID0gImdyZXkyMCIpICsKICBmYWNldF93cmFwKH4gQ2x1c3RlciwgbGFiZWxsZXIgPSBsYWJlbF9ib3RoLCBuY29sID0gMykgKwogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDEiKSArCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT00KSkpCmBgYAoKCmBgYHtyfQp0b3BfcnN2IDwtIHNlcUNsdXN0ZXJzX2ZwY2EuZGlldCAlPiUKICBncm91cF9ieShTZXFfSUQsIENsdXN0ZXIpICU+JQogIHN1bW1hcmlzZShtZWFuX2FibmQgPSBtZWFuKHZhbHVlKSkgJT4lCiAgYXJyYW5nZShkZXNjKG1lYW5fYWJuZCkpICU+JQogIGxlZnRfam9pbihUQVhUQUIgJT4lIHNlbGVjdChTZXFfSUQsIE9yZ05hbWUsIFNwZWNpZXMsIEdlbnVzKSkKCnRvcF9nZW51cyA8LSB0b3BfcnN2ICU+JQogIGZpbHRlcighaXMubmEoR2VudXMpKSAlPiUKICBncm91cF9ieShHZW51cywgQ2x1c3RlcikgJT4lCiAgc3VtbWFyaXNlKAogICAgbWVhbl9hYm5kID0gbWVhbihtZWFuX2FibmQpLAogICAgcHJldiA9IG4oKSkgJT4lCiAgYXJyYW5nZShDbHVzdGVyLCAtcHJldiwgLW1lYW5fYWJuZCkgCmBgYAoKYGBge3J9Cm4gPSA2OyBuY2x1c3QgPSBsZW5ndGgodW5pcXVlKHRvcF9nZW51cyRDbHVzdGVyKSkKZGZfdG9wX2dlbnVzIDwtIHRvcF9nZW51cyAlPiUgCiAgZ3JvdXBfYnkoQ2x1c3RlcikgJT4lIAogIHRvcF9uKG4sIHd0ID0gcHJldiptZWFuX2FibmQpICU+JQogIGFycmFuZ2UoQ2x1c3RlciwgLXByZXYsIC1tZWFuX2FibmQpICU+JQogIHNlbGVjdCgtbWVhbl9hYm5kLCAtcHJldikgJT4lIHVuZ3JvdXAoKSAlPiUKICBtdXRhdGUoQ2x1c3RlciA9IHBhc3RlMCgiQ2x1c3RlciIsIENsdXN0ZXIpKSAlPiUKICBtdXRhdGUoaWR4ID0gcmVwKDE6biwgbmNsdXN0KSkgJT4lCiAgc3ByZWFkKGtleSA9IENsdXN0ZXIsIEdlbnVzKQpkZl90b3BfZ2VudXMgJT4lIHNlbGVjdCgtaWR4KQpgYGAKCgpgYGB7cn0KbiA9IDEwOyBuY2x1c3QgPSBsZW5ndGgodW5pcXVlKHRvcF9yc3YkQ2x1c3RlcikpCmRmX3RvcF9yc3YgPC0gdG9wX3JzdiAlPiUgCiAgdW5ncm91cCgpICU+JQogIHNlbGVjdChPcmdOYW1lLCBDbHVzdGVyLCBtZWFuX2FibmQpICU+JQogIGdyb3VwX2J5KENsdXN0ZXIpICU+JSAKICB0b3BfbihuLCB3dCA9IG1lYW5fYWJuZCkgJT4lCiAgYXJyYW5nZShDbHVzdGVyLCBtZWFuX2FibmQpICU+JQogIHNlbGVjdCgtbWVhbl9hYm5kKSAlPiUgdW5ncm91cCgpICU+JQogIG11dGF0ZShDbHVzdGVyID0gcGFzdGUwKCJDbHVzdGVyIiwgQ2x1c3RlcikpICU+JQogIG11dGF0ZShpZHggPSByZXAoMTpuLCBuY2x1c3QpKSAlPiUKICBzcHJlYWQoa2V5ID0gQ2x1c3RlciwgT3JnTmFtZSkKZGZfdG9wX3JzdiAlPiUgc2VsZWN0KC1pZHgpCmBgYAoKCiMjIENvbG9uIGNsZWFub3V0CgoKYGBge3IgYnJheS1jY30KYnJheV90b19iYXNlbGluZV9mbHRyICU+JSAKICBmaWx0ZXIocGVydHVyYmF0aW9uID09ICJDQyIsIFJlbERheSA+PSAtNTAsIFJlbERheSA8PSA1MCkgJT4lCiAgbXV0YXRlKEludGVydmFsID0gZmFjdG9yKEludGVydmFsLCBsZXZlbCA9IG5hbWVzKGNjX2ludHZfY29scykpKSAlPiUKICBnZ3Bsb3QoCiAgICBhZXMoeCA9IFJlbERheSwgeSA9IGRpc3RfdG9fYmFzZWxpbmUsIAogICAgICAgIGdyb3VwID0gU3ViamVjdCwgY29sb3IgPSBJbnRlcnZhbCkpICsKICBnZW9tX2xpbmUoYWVzKGdyb3VwID0gU3ViamVjdCksIGFscGhhID0gMC43LCBsd2QgPSAwLjUpICsgCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSwgc2l6ZSA9IDEuMikgKyAKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY2NfaW50dl9jb2xzKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSArIAogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTMpKSkgKwogIHhsYWIoIkRheXMgZnJvbSBjb2xvbiBjbGVhbm91dCIpICsKICB5bGFiKCJCcmF5LUN1cnRpcyBkaXN0YW5jZSB0byA3IHByZS1kaWV0IHNhbXBsZXMiKSAKYGBgCgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZnBjYS5icmF5LmNjMzAgPC0gZml0X2Rpc3RfdG9fYmFzZWxpbmUoCiAgYnJheV90b19iYXNlbGluZV9mbHRyICU+JSAKICAgIGZpbHRlcihwZXJ0dXJiYXRpb24gPT0gIkNDIiwgUmVsRGF5ID49IC0zMCwgUmVsRGF5IDw9IDMwKSAlPiUKICAgIGFycmFuZ2UoU3ViamVjdCwgUmVsRGF5KSkKCnNhdmUobGlzdCA9IGMoImZwY2EuYnJheS5hYngiLCAiZnBjYS5icmF5LmRpZXQiLCJmcGNhLmJyYXkuZGlldDMwIiwgCiAgICAgICAgICAgICAgImZwY2EuYnJheS5jYyIsICJmcGNhLmJyYXkuY2MzMCIpLCAKICAgICBmaWxlID0gIm91dHB1dC9mcGNhX3Jlcy5yZGEiKQpgYGAKCmBgYHtyIGZwY2EtYnJheS1jYywgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTYuNX0KKHBDQyA8LSBicmF5X3RvX2Jhc2VsaW5lX2ZsdHIgJT4lIAogIGZpbHRlcihwZXJ0dXJiYXRpb24gPT0gIkNDIiwgUmVsRGF5ID49IC0zMCwgUmVsRGF5IDw9IDMwKSAlPiUKZ2dwbG90KGFlcyh4ID0gUmVsRGF5LCB5ID0gZGlzdF90b19iYXNlbGluZSkpICsKICBnZW9tX2xpbmUoCiAgICBkYXRhID0gZnBjYS5icmF5LmNjMzBbWyJmaXR0ZWQiXV0sCiAgICBhZXMoZ3JvdXAgPSBTdWJqZWN0LCB4ID0gdGltZSwgeSA9IHZhbHVlKSwKICAgIGFscGhhID0gMC4zLCBzaXplID0gMC43LCBjb2xvciA9ICJncmV5MzAiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgbHdkID0gMSwgY29sb3IgPSAib3JhbmdlIikgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDQsIGx3ZCA9IDEsIGNvbG9yID0gIm9yYW5nZSIpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IEludGVydmFsKSwgc2l6ZSA9IDEuNSwgYWxwaGEgPSAwLjcpICsKICBnZW9tX2xpbmUoCiAgICBkYXRhID0gZnBjYS5icmF5LmNjMzBbWyJtZWFuIl1dLCBhZXMoeCA9IHRpbWUsIHkgPSB2YWx1ZSksCiAgICBjb2xvciA9ICJuYXZ5Iiwgc2l6ZSA9IDIpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY2NfaW50dl9jb2xzLCBuYW1lID0gIkludGVydmFsIikgKwogIHNjYWxlX3hfY29udGludW91cygKICAgIG5hbWUgPSAiRGF5cyBmcm9tIGNvbG9uIGNsZWFub3V0IiwKICAgIGxpbWl0cyA9IGMoTkEsIE5BKSwgYnJlYWtzID0gc2VxKC01MCwgNjAsIDEwKSkgKwogIHNjYWxlX3lfY29udGludW91cygKICAgIG5hbWUgPSAiQnJheS1DdXJ0aXMgZGlzdGFuY2UgdG8gYmFzZWxpbmUiLCAKICAgIGxpbWl0cyA9IGMoTkEsIE5BKSwgYnJlYWtzID0gc2VxKDAuMSwgMC44MCwgMC4xKSkgKwogIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIwKSkpIApgYGAKCgpgYGB7ciBmcGNhLWRlcml2LWJyYXktY2MsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD02LjV9CmZwY2EuYnJheS5jYzMwW1siZml0dGVkIl1dIDwtIGZwY2EuYnJheS5jYzMwW1siZml0dGVkIl1dICU+JQogIG11dGF0ZShJbnRlcnZhbCA9IGlmZWxzZShmcGNhLmJyYXkuY2MzMFtbImZpdHRlZCJdXSR0aW1lIDwgMCAsICJQcmVDQyIsIlBvc3RDQyIpKQoKKHBDQ0Rlcml2IDwtICBmcGNhLmJyYXkuY2MzMFtbImZpdHRlZCJdXSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBSZWxEYXkpKSArCiAgZ2VvbV9saW5lKAogICAgYWVzKGdyb3VwID0gU3ViamVjdCwgeCA9IHRpbWUsIHkgPSBkZXJpdiwgY29sb3IgPSBJbnRlcnZhbCksCiAgICBhbHBoYSA9IDAuNSwgc2l6ZSA9IDAuNykgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIGx3ZCA9IDEsIGNvbG9yID0gIm9yYW5nZSIpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSA0LCBsd2QgPSAxLCBjb2xvciA9ICJvcmFuZ2UiKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGNjX2ludHZfY29scywgbmFtZSA9ICJJbnRlcnZhbCIpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobmFtZSA9ICIiLAogICAgbGltaXRzID0gYyhOQSwgTkEpLCBicmVha3MgPSBzZXEoLTUwLCA2MCwgMTApKSArCiAgeWxhYigiRGVyaXZhdGl2ZSIpKQpgYGAKCgpgYGB7ciBmcGNhLWJyYXktY2MtbGFicywgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTYuNX0KKHBDQ0xhYiA8LSBicmF5X3RvX2Jhc2VsaW5lX2ZsdHIgJT4lIAogIGZpbHRlcihwZXJ0dXJiYXRpb24gPT0gIkNDIiwgUmVsRGF5ID49IC01MCwgUmVsRGF5IDw9IDUwKSAlPiUKZ2dwbG90KGFlcyh4ID0gUmVsRGF5LCB5ID0gZGlzdF90b19iYXNlbGluZSkpICsKICBnZW9tX2xpbmUoCiAgICBkYXRhID0gZnBjYS5icmF5LmNjW1siZml0dGVkIl1dLAogICAgYWVzKGdyb3VwID0gU3ViamVjdCwgeCA9IHRpbWUsIHkgPSB2YWx1ZSksCiAgICBhbHBoYSA9IDAuMywgc2l6ZSA9IDAuNyApICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBsd2QgPSAxLCBjb2xvciA9ICJvcmFuZ2UiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gNCwgbHdkID0gMSwgY29sb3IgPSAib3JhbmdlIikgKwogIGdlb21fdGV4dCgKICAgICAgYWVzKGxhYmVsID0gU3ViamVjdCwgY29sb3IgPSBJbnRlcnZhbCksIHNpemUgPSA0LCBhbHBoYSA9IDAuNykgKwogIGdlb21fbGluZSgKICAgIGRhdGEgPSBmcGNhLmJyYXkuY2NbWyJtZWFuIl1dLCBhZXMoeCA9IHRpbWUsIHkgPSB2YWx1ZSksCiAgICBjb2xvciA9ICJuYXZ5Iiwgc2l6ZSA9IDIpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY2NfaW50dl9jb2xzLCBuYW1lID0gIkludGVydmFsIikgKwogIHNjYWxlX3hfY29udGludW91cygKICAgIG5hbWUgPSAiRGF5cyBmcm9tIGNvbG9uIGNsZWFub3V0IiwKICAgIGxpbWl0cyA9IGMoTkEsIE5BKSwgYnJlYWtzID0gc2VxKC01MCwgNjAsIDEwKSkgKwogIHNjYWxlX3lfY29udGludW91cygKICAgIG5hbWUgPSAiQnJheS1DdXJ0aXMgZGlzdGFuY2UgdG8gYmFzZWxpbmUiLCAKICAgIGxpbWl0cyA9IGMoMC4xLCAwLjg1KSwgYnJlYWtzID0gc2VxKDAuMSwgMC44MCwgMC4xKSkgKwogIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIwKSkpCgpgYGAKCgpgYGB7ciBmcGNhLWJyYXktY2MtdmFsLWRlcml2LCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9Ni41fQp2cCA9IGdyaWQ6OnZpZXdwb3J0KHdpZHRoID0gMC4zLCBoZWlnaHQgPSAwLjMyLCB4ID0gMC4wNywgeSA9MC45NSwganVzdCA9IGMoImxlZnQiLCAidG9wIikpCnByaW50KHBDQykKcHJpbnQocENDRGVyaXYgKyB0aGVtZV9idyhiYXNlX3NpemUgPSAxMCkgKyB0aGVtZV9zdWJwbG90LCB2cCA9IHZwKQpgYGAKCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAoK